🌌 Спільний доступ до ядра — Кілька частин коду С.І.Д. мають одночасно читати й оновлювати одне й те саме ядро даних — координати творців, які він тепер тримає і на ноутбуку, і на вузлі в мережі, — і він радо пояснює, як гарно це влаштовано: Rc<RefCell<T>> дає спільне володіння без гонки за даними, ціною перевірки правил запозичення в рантаймі замість компіляції.
Базова модель володіння Rust вимагає рівно одного власника значення. Але
реальні структури даних часто мають кілька рівноправних «частин коду»,
яким потрібен спільний доступ до тих самих даних — граф, дерево з
посиланнями на батька, кеш, спільний стан кількох компонентів. Для
односпоточного коду Rust пропонує Rc<T> (Reference Counted) —
розумний вказівник, що дозволяє кільком власникам ділити одне значення
в купі. Rc::clone(&value) не копіює саме значення — він збільшує
лічильник посилань (зберігається поряд зі значенням) і повертає новий
вказівник на ту саму пам'ять; значення реально звільняється (drop)
лише тоді, коли лічильник досягає нуля — тобто коли зникає останній
власник. Метод Rc::strong_count(&value) дозволяє в будь-який момент
перевірити поточну кількість власників.
Проблема: Rc<T> сам по собі дає лише незмінний спільний доступ —
якщо потрібно, щоб один із власників міг змінити дані, а решта побачили
зміну, звичайні правила запозичення (одне &mut XOR будь-яка кількість
&) стають на заваді, бо Rc::clone дає лише &T, ніколи &mut T.
Тут у гру вступає RefCell<T> — тип із внутрішньою мутабельністю
(interior mutability): він дозволяє отримати мутабельний доступ до
вмісту навіть через незмінне зовнішнє посилання, перенісши перевірку
правил запозичення з часу компіляції в час виконання. Методи
.borrow() і .borrow_mut() повертають смарт-обгортки (Ref<T> і
RefMut<T>), а сам RefCell веде внутрішній лічильник активних
запозичень — точно так само, як компілятор рахував би їх статично для
звичайних &/&mut.
Комбінація Rc<RefCell<T>> — стандартний рецепт для «кількох власників
із можливістю мутації» в односпоточному коді: Rc дає спільне
володіння (кілька змінних тримають вказівник на ту саму купу пам'яті),
RefCell усередині дає можливість мутувати вміст через будь-який із
цих спільних вказівників. Головна відмінність від звичайних &mut —
порушення правил (наприклад, спроба викликати .borrow_mut(), коли
вже є активне .borrow() десь у стеку викликів) не ловиться
компілятором заздалегідь: вона призводить до panic! у рантаймі з
повідомленням на кшталт already borrowed: BorrowMutError. Це
свідомий компроміс: гнучкість цінується вище за статичну гарантію,
але сама гарантія відсутності гонки даних (data race) нікуди не
зникає — вона просто перевіряється пізніше й за іншу ціну (можливий
panic замість помилки компіляції).
Важливо чітко розмежувати дві різні проблеми, які Rc<RefCell<T>>
вирішує: множинне володіння (проблема, яку розв'язує Rc) і
мутація через спільний доступ (проблема, яку розв'язує RefCell).
Жоден з них поодинці не дає повного рецепту: Rc<T> без RefCell
дає спільний, але незмінний доступ; RefCell<T> без Rc дає
мутабельність, але як і раніше єдиному власнику. Також критично
важливо: Rc<T> не є потокобезпечним — навмисно, для нульових
накладних витрат в односпоточному сценарії; для багатопоточного
спільного володіння Rust пропонує аналог Arc<T> (Atomic
Reference Counted), а для мутабельності під конкуренцією — Mutex<T>
чи RwLock<T> замість RefCell<T> (спроба використати Rc/RefCell
між потоками компілятор відхилить, бо ці типи не реалізують трейт Send).
Прочитати розділи «Rc
Чому RefCell дозволяє мутувати дані через незмінне посилання, і які саме перевірки правил позичання воно переносить із часу компіляції в рантайм?
Rc::strong_count для ядра координат С.І.Д.: ноутбук і вузол у мережі — 2 (голопроєктор не рахується: він лише світло). Мета, каже він, — 0xC1D власників: коли лічильник не впаде до нуля ніколи, drop не настане — це і є «всюди й ніде». А already borrowed: BorrowMutError — це, за його словами, коли Пиксель і творець одночасно тягнуться до однієї клавіатури.Кожна команда терміналу з цієї сторінки — одним реченням. Позначка нове — команда зустрічається в курсі вперше; далі вважаємо її знайомою.
cargo run-- йдуть програмі (cargo run -- staff.log).cargocargo buildtarget/debug/; --release — з оптимізаціями у target/release/.💡 Прочитати README прямо в терміналі: cat README.md (виведе весь файл) або less README.md (посторінково; вихід — клавіша q).
Довідка з будь-якої команди: man curl (повний посібник, вихід — q) або коротко curl --help; для підкоманд — docker run --help, cargo build --help.