Наглядная иллюстрация того, что может случиться с 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++, сохраняя при этом удобство использования.
Если статья была полезной, вас могут заинтересовать и другие статьи в моём телеграм-канале.