Внутрішня мутабельність

У деяких ситуаціях необхідно модифікувати дані за спільним (доступним лише для читання) посиланням. Наприклад, структура даних зі спільним доступом може мати внутрішній кеш, і ви хочете оновити цей кеш методами, доступними лише для читання.

Паттерн "внутрішня мутабельність" дозволяє ексклюзивний (мутабельний) доступ за спільним посиланням. Стандартна бібліотека надає декілька способів зробити це, забезпечуючи при цьому безпеку, як правило, шляхом виконання перевірки під час виконання.

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.

Завдяки реалізації DerefDerefMut для 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:?}");
}
This slide should take about 10 minutes.

Основне, що можна винести з цього слайду, це те, що Rust надає безпечні способи модифікації даних за спільним посиланням. Існує безліч способів забезпечити цю захищеність, і RefCell та Cell - два з них.

  • RefCellзастосовує звичайні правила запозичень Rust (або декілька спільних посилань, або одне ексклюзивне посилання) з перевіркою під час виконання. У цьому випадку всі запозичення дуже короткі і ніколи не перетинаються, тому перевірки завжди проходять успішно.

    • Додатковий блок у прикладі RefCell призначений для завершення запозичення, створеного викликом borrow_mut, до того, як ми надрукуємо комірку. Спроба надрукувати запозичену комірку RefCell просто покаже повідомлення "{borrowed}".
  • Cell є найпростішим засобом гарантування безпеки: він має метод set, який приймає значення &self. Це не потребує перевірки під час виконання, але вимагає переміщення значень, що може мати свою ціну.

  • І RefCell, і Cell є !Sync, що означає, що &RefCell і &Cell не можна передавати між потоками. Це запобігає спробам двох потоків одночасно отримати доступ до комірки.