Наглядная иллюстрация того, что может случиться с C++ программистами.

В Rust необычная схема управления памятью. Он не использует сборщик мусора, как в Java и Go, что делает его быстрым. Скорость Rust сопоставима со скоростью C.

Однако и у этой схемы есть минусы. Для того чтобы их решить, были введены умные указатели, которые дают возможность оперировать памятью на низком уровне с тем же удобством.

В статье специально использованы простейшие примеры, чтобы понять их было легче.

Типы

Box

Нужен для хранения объектов в куче, а не на стеке.

Обычно используется для рекурсивных типов, где размер объекта неизвестен во время компиляции.

Пример кода, который не будет работать.

struct Expression {
    operator: Operator,
    left: Expression, // Ошибка: recursive type `Expression` has infinite size
    right: Expression, // Ошибка: recursive type `Expression` has infinite size
}

Чинится обертыванием left и right в Box.

struct Expression {
    operator: Operator,
    left: Box<Expression>,
    right: Box<Expression>,
}

Rc

Позволяет нескольким переменным владеть одним объектом размещенным в куче.

Не работающий код.

let a = "Hello, World!".to_string();
let b = a;
let c = a; // Ошибка: use of moved value

Чтобы он заработал, добавим Rc.

let a = Rc::new("Hello, World!".to_string());
let b = Rc::clone(&a);
let c = Rc::clone(&a);

Код также будет работать если мы скопируем объект.

let a = "Hello, World!".to_string();
let b = a.clone();
let c = a;

Но прямое копирование может серьезно повредить производительности. Преимущество Rc в том, что при присваивании не создаётся новый объект, а даётся ссылка на уже существующий.

Arc

То же что и Rc, но безопасное для использования в многопоточных приложениях. Это значит, что его можно использовать из разных потоков, не боясь гонок данных.

let a = Arc::new(1);
let b = Arc::clone(&a);
let c = Arc::clone(&a);

Дороже с точки зрения производительности из-за способа подсчёта ссылок.

RefCell

Позволяет изменять данные внутри себя даже если объявлен как неизменяемый.

let a = RefCell::new(1);

*a.borrow_mut() += 1;

dbg!(a); // 2

Комбо

RefCell часто комбинируют с Rc в виде Rc<RefCell<T>>. Это позволяет каждому владельцу ссылки изменять общий объект.

let a = Rc::new(RefCell::new(1));
let b = Rc::clone(&a);
let c = Rc::clone(&a);

*b.borrow_mut() += 1;
dbg!(&a); // 2
dbg!(&c); // Тоже 2

*c.borrow_mut() += 1;
dbg!(&a); // 3
dbg!(&b); // Тоже 3

Заключение

Главное преимущество умных указателей – избегание ошибок типа segfault и выстрелов в ногу, характерных для C и C++, сохраняя при этом удобство использования.

Если статья была полезной, вас могут заинтересовать и другие статьи в моём телеграм-канале.