Представьте ситуацию: у вас есть данные в куче (heap), и вы хотите, чтобы несколько независимых объектов могли одновременно ими пользоваться. В обычных языках программирования ссылки раздаются направо и налево, но в Rust строгая система владения требует четкого понимания: кто является единственным владельцем ресурса?
Если владельцев должно быть несколько одновременно (в пределах одного потока), на сцену выходит умный указатель Rc<T> (Reference Counted).
Rc<T> — это управляющий указатель с подсчетом ссылок для однопоточных сценариев.
Каждый раз, когда вы делаете Rc::clone(&a), вы не копируете сами данные внутри кучи. Вместо этого вы просто увеличиваете служебный счетчик сильных ссылок — strong_count — на 1. А когда очередная переменная выходит из области видимости, drop() уменьшает strong_count на 1.
Как только strong_count становится равен 0, память кучи автоматически очищается!
Примечание
Почему бы не использовать обычную ссылку &T?
&T — это временное заимствование. Ссылка жестко ограничена временем жизни (lifetimes), её нельзя просто так сохранить в структуре без явной аннотации 'a. Rc<T> решает эту проблему, давая независимое разделяемое владение.
С Rc можно безопасно использовать одни и те же данные в нескольких структурах. Владение разделено, а данные освобождаются когда последний владелец исчезает.
Результат выполнения:
2. Связка Rc + RefCell для внутренней мутабельности#
Сам по себе Rc<T> дает только иммутабельный доступ к имеющимся данным. Если вы попробуете изменить значение внутри Rc, компилятор Rust выдаст ошибку.
Что делать, если несколько объектов должны не только читать, но и динамически менять общее состояние?
Ответ — обернуть данные в RefCell<T>, получив комбинацию Rc<RefCell<T>>.
RefCell переносит проверки правил заимствования (borrow checker) с этапа компиляции на этап выполнения программы (runtime).
Если вы случайно попытаетесь взять две изменяемые ссылки borrow_mut() одновременно на один и тот же RefCell, программа аварийно завершится с паникой!
Чтобы разорвать замкнутый круг циклических ссылок, Rust предоставляет ослабленные ссылки — Weak<T>.
Сильная ссылка (Rc<T>) — вы выражаете прямое владение объектом. Объект существует до тех пор, пока strong_count > 0.
Слабая ссылка (Weak<T>) — вы выражаете наблюдение за объектом без владения. Создается вызовом Rc::downgrade(&rc).
Weak<T> увеличивает служебный счетчик weak_count, но не влияет на strong_count. Это означает, что главный объект в куче спокойно очистится при обнулении strong_count, даже если на него до сих пор смотрят слабые ссылки!
Так как объект, на который указывает Weak<T>, мог быть уже очищен в памяти, вы не можете разыменовать Weak напрямую. Для этого используется метод .upgrade():
letweak_ref: Weak<Gadget>=Rc::downgrade(&gadget);// Пытаемся временно превратить слабую ссылку обратно в сильную:
ifletSome(strong_ref)=weak_ref.upgrade(){println!("Объект еще жив: {}",strong_ref.id);}else{println!("Объект уже деаллоцирован!");}
Давайте посмотрим на проблему циклов и их решение через Weak на интерактивных слайдах:
Деревья и Графы: Узлы хранят сильные ссылки Rc на дочерние элементы, а дочерние элементы хранят слабые ссылки Weak на родительский узел (Parent pointers).
Кэширование: Кэш хранит Weak ссылки на объекты. Если объект больше никому в программе не нужен, он автоматически выгружается из памяти.
Событийно-ориентированные системы: Подписчики на события хранят Weak ссылки на слушателей.
Пройдите короткий тест, чтобы закрепить знания по работе с Rc, RefCell, цикличным ссылкам и типажу Weak<T>.
Не забывайте, что Rc и Weak предназначены для однопоточного кода. ❗️
Если вам нужно делиться данными между несколькими параллельными потоками — для этого используется потокобезопасная пара Arc<T> и std::sync::Weak<T>, подробный разбор которой вы найдете в отдельной статье про Arc!
История обновлений
Добавил подробный разбор циклических ссылок (Rc cycles), проблем утечек памяти и слабого владения через Weak (downgrade/upgrade).
Статья прочитана
Пожалуйста, оцените насколько статья была вам полезна и понятна