Управление памятью в Rust:
мифы и реальность
Разбираем работу Borrow Checker на примере кэширования данных в реальном приложении. Почему RefCell спасает, когда Rc не справляется.
01. Введение
Rust обещает безопасность памяти без сборщика мусора. Звучит как магия, пока вы не сталкиваетесь с компилятором, который отказывается собирать ваш код, потому что вы пытались изменить данные, на которые есть несколько ссылок.
В этой статье мы рассмотрим конкретный кейс: реализацию простого кэша конфигурации, который должен быть доступен для чтения из нескольких потоков, но обновляться только из одного места. Мы пройдем путь от наивного использования Option до безопасной мутации через RefCell и Cell.
> "Если вы чувствуете, что хотите использовать unsafe, чтобы обойти заимствование — остановитесь. Rust уже нашел решение лучше, вы просто еще не узнали его."
02. Технический разбор
Начнем с проблемы. У нас есть структура ConfigStore. Мы хотим хранить в ней значение типа String. В C++ мы бы просто передавали указатель. В Rust мы должны явно указать: кому принадлежит память и кто может ее изменять.
Сценарий А: Rc
Rc (Reference Counted) позволяет иметь множественное владение данными. Но есть нюанс: Rc неизменяем по умолчанию.
error[E0594]: cannot assign to `*data`, which is declared as read-only
Сценарий Б: RefCell
Решение — перенести проверку заимствований со времени компиляции на время выполнения. RefCell позволяет менять данные внутри, даже если сам контейнер не мутируемый.
let mut store = RefCell::new("v1.0".to_string());
*store.borrow_mut() = "v1.1".to_string();
Ключевое отличие: Rc считает количество ссылок, а RefCell следит за тем, чтобы в момент мутации не было активных читалок (borrow). Если попытаться нарушить правила, программа паникует (panic) в рантайме, но компилятор пропустит код. Это цена, которую мы платим за гибкость.
03. Выводы
Управление памятью в Rust — это не про то, как освободить байты. Это про то, как выразить намерения вашей программы на языке типов.
-
01.
Используйте
Copyдля простых примитивов (int, bool), чтобы избежать накладных расходов. -
02.
Если данные читаются многими, но меняются редко —
Rcотличный выбор для синглотон-паттернов в однопоточном окружении.> -
03.
Не бойтесь
clone()для небольших структур. Это часто дешевле, чем усложнять архитектуру заимствований.
Хотите разбираться глубже?
В курсе "Rust Internals" мы разбираем 15 кейсов управления памятью и аллокации кучи.