ПрограммированиеBackend разработчик

Как работают smart pointers в Rust (Box, Rc, Arc, RefCell)? Чем они отличаются друг от друга, и в каких случаях какой стоит выбирать?

Проходите собеседования с ИИ помощником Hintsage

Ответ

В Rust нет классического сборщика мусора, поэтому для управления владением сложными структурами используют умные указатели (smart pointers). Наиболее часто используются следующие:

  • Box<T> — выделяет память под объект в куче и передаёт владение ею. Используется для случаев, когда размер данных не известен во время компиляции или требуется перемещаемый, но уникальный ресурс.

  • Rc<T> (Reference Counted) — подсчёт ссылок, позволяет нескольким переменным «разделять» владение неизменяемыми данными (только в однопоточном контексте).

  • Arc<T> (Atomic Reference Counted) — реализует также подсчёт ссылок, но атомарный; применение допустимо в многопоточных программах.

  • RefCell<T> — предоставляет "внутреннее мутабельное" владение на этапе выполнения, позволяя менять содержимое даже через неизменяемую ссылку, но только в одном потоке (не потокобезопасно!).

Пример:

use std::rc::Rc; let a = Rc::new(vec![1,2,3]); let b = Rc::clone(&a); // Теперь и a, и b - владельцы одних данных

Вопрос с подвохом

Можно ли использовать Rc<T> в многопоточном коде, если все потоки только читают данные? Объясните.

Ответ: Нет, нельзя! Несмотря на то, что Rc<T> позволяет только неизменяемый доступ к данным, сам контейнер Rc<T> не потокобезопасен, т.к. внутреннее число ссылок не защищено от гонок данных. Для этого предназначен Arc<T> — его внутренний счётчик потокобезопасен.

Пример:

// Следующий код не скомпилируется! use std::thread; use std::rc::Rc; let five = Rc::new(5); for _ in 0..10 { let five = Rc::clone(&five); thread::spawn(move || { println!("{}", five); }); }

Примеры реальных ошибок из-за незнания тонкостей темы


История

Пытались использовать Rc<T> для разделения кэша между потоками для ускорения веб-сервиса. На бою получили странные краши и повреждённые данные. После расследования выяснилось, что Rc не потокобезопасен, и счетчик ссылок был повреждён. Решение: замена на Arc<T>.

История

В desktop-приложении большое дерево объектов хранили в Box<T>, но не учли, что несколько частей UI должны разделять владение данными. Это привело к ошибкам компиляции. Решением стало использование Rc<T> для разделения доступа.

История

В модуле бизнес-логики использовали RefCell<T> для организации мутабельного доступа к данным, которые также передавались по Arc<T> между потоками. Но попытка совместить RefCell<T> и Arc<T> привела к гонкам и панике во время исполнения. Для потокобезопасного варианта следовало использовать Mutex<T> или RwLock<T> вместо RefCell<T>.