Одна из основных целей Rust — не допустить изменения неизменяемых данных и избежать гонок данных на этапе компиляции. В обычных условиях Rust не позволит менять данные через немутируемую ссылку. Однако в системах с кэшированием, ленивыми вычислениями или логикой, требующей изменения внутреннего состояния через ссылку, это бывает необходимо. Для этого и появился паттерн interior mutability.
Без interior mutability реализовать кэши, ленивую инициализацию и многие другие идиомы сложно или невозможно, сохраняя безопасное владение и ссылки. Классический пример — кэш внутри функции, которая предоставляет внешнему миру только неизменяемую ссылку на себя.
В Rust есть специальные типы — Cell и RefCell (и их аналоги для многопоточности), которые позволяют изменять внутреннее значение даже через немутируемую ссылку, контролируя безопасность на этапе выполнения, а не компиляции.
Cell<T> — копи-примитив, позволяет изменять или читать значение, не используя ссылок, но только для типов, реализующих Copy.RefCell<T> — позволяет получать мутабельную ссылку "на лету" даже при наличии только немутируемой внешней ссылки, но если попытаться получить одновременно две мутабельные или одну мутабельную и несколько немутируемых — произойдёт паника во время выполнения.Пример кода:
use std::cell::RefCell; struct Foo { cache: RefCell<Option<u32>>, } impl Foo { fn get_or_compute(&self) -> u32 { if let Some(val) = *self.cache.borrow() { return val; } let computed = 42; *self.cache.borrow_mut() = Some(computed); computed } }
Ключевые особенности:
Можем ли мы безопасно использовать RefCell для многопоточных структур?
Нет, RefCell не потокобезопасен. Для работы в многопоточной среде используйте Mutex или RwLock.
Можно ли вернуть ссылку на содержимое Cell<T>?
Нет, Cell не выдаёт никаких ссылок, только копирует или обновляет значение. Работает только с Copy-типами, для всех остальных используйте RefCell.
Что произойдёт, если вызвать borrow_mut дважды подряд на одном и том же RefCell?
Произойдёт panic в рантайме, т.к. RefCell отслеживает количество активных ссылок. Вторая попытка получить мутабельный доступ при уже существующей ссылке приведёт к панике.
В проекте для хранения внутри структуры любого изменяемого состояния всегда применяют RefCell, даже если можно использовать мутабельное владение. Код становится многословным, появляются паники в рантайме, а тестировать становится сложно.
Плюсы: Можно быстро обойти ограничения компилятора и добиться нужного поведения
Минусы: Высокий риск ошибок на этапе исполнения, падение приложения, сложная отладка логики
RefCell используют только для реализации ленивых кэшей в крупных структурах, а в остальном коде остаются при классическом владении и ссылках. Все значения очищаются правильно, нет паник.
Плюсы: Прозрачная логика, минимизация использования interior mutability, код устойчив и предсказуем
Минусы: Требует внимательности к границам изменения данных: чтение кеша всегда безопасно, запись только по делу и в узко определённых местах