Внутрішня мутабельність
У деяких ситуаціях необхідно модифікувати дані за спільним (доступним лише для читання) посиланням. Наприклад, структура даних зі спільним доступом може мати внутрішній кеш, і ви хочете оновити цей кеш методами, доступними лише для читання.
Паттерн "внутрішня мутабельність" дозволяє ексклюзивний (мутабельний) доступ за спільним посиланням. Стандартна бібліотека надає декілька способів зробити це, забезпечуючи при цьому безпеку, як правило, шляхом виконання перевірки під час виконання.
Cell
Cell
обгортає значення і дозволяє отримати або встановити значення, використовуючи лише спільне посилання на Cell
. Однак, вона не дозволяє жодних посилань на внутрішнє значення. Оскільки посилань немає, правила запозичення не можуть бути порушені.
use std::cell::Cell; fn main() { // Зауважте, що `cell` НЕ оголошено як мутабельну. let cell = Cell::new(5); cell.set(123); println!("{}", cell.get()); }
RefCell
RefCell
дозволяє отримувати доступ до обгорнутого значення та змінювати його, надаючи альтернативні типи Ref
та RefMut
, які імітують &T
/&mut T
, не будучи насправді Rust посиланнями.
Ці типи виконують динамічні перевірки за допомогою лічильника в RefCell
, щоб запобігти існуванню RefMut
поряд з іншим Ref
/RefMut
.
Завдяки реалізації Deref
(і DerefMut
для RefMut
), ці типи дозволяють викликати методи за внутрішнім значенням, не дозволяючи посиланням втекти.
use std::cell::RefCell; fn main() { // Зауважте, що `cell` НЕ оголошено як мутабельну. let cell = RefCell::new(5); { let mut cell_ref = cell.borrow_mut(); *cell_ref = 123; // Це спричиняє помилку під час виконання. // let other = cell.borrow(); // println!("{}", *other); } println!("{cell:?}"); }
Основне, що можна винести з цього слайду, це те, що Rust надає безпечні способи модифікації даних за спільним посиланням. Існує безліч способів забезпечити цю захищеність, і RefCell
та Cell
- два з них.
-
RefCell
застосовує звичайні правила запозичень Rust (або декілька спільних посилань, або одне ексклюзивне посилання) з перевіркою під час виконання. У цьому випадку всі запозичення дуже короткі і ніколи не перетинаються, тому перевірки завжди проходять успішно.- Додатковий блок у прикладі
RefCell
призначений для завершення запозичення, створеного викликомborrow_mut
, до того, як ми надрукуємо комірку. Спроба надрукувати запозичену коміркуRefCell
просто покаже повідомлення"{borrowed}"
.
- Додатковий блок у прикладі
-
Cell
є найпростішим засобом гарантування безпеки: він має методset
, який приймає значення&self
. Це не потребує перевірки під час виконання, але вимагає переміщення значень, що може мати свою ціну. -
І
RefCell
, іCell
є!Sync
, що означає, що&RefCell
і&Cell
не можна передавати між потоками. Це запобігає спробам двох потоків одночасно отримати доступ до комірки.