Уроки модуля (2/2)
Умные указатели: коробка и пульт
Умный указатель – это структура данных, которая ведет себя как указатель, но имеет метаданные и дополнительные возможности (например, подсчет ссылок или автоматическое управление памятью).
В Rust обычные ссылки (например, &) заимствуют только данные. С другой стороны, умные указатели часто владеют данными, на которые они указывают. Общие примеры включают String и Vec<T>, но стандартная библиотека предоставляет и другие специализированные.
Box<T> для распределения кучи
Самый простой интеллектуальный указатель — Box<T>, который хранит данные в куче, а не в стеке. В стеке остается только указатель на данные, выделенные в куче.
В основном его используют в следующих случаях:
- Если у вас есть тип, размер которого не может быть известен во время компиляции (например, рекурсивный тип), и вы хотите использовать значение этого типа в контексте, требующем точного размера.
- Если вы хотите передать право собственности на большой объем данных без их копирования.
Базовый пример:
fn main() {
let b = Box::new(5); // allocates the value 5 on the heap
println!("b = {}", *b); // dereferences with * to read the value
}Рекурсивные типы и списки минусов
Рекурсивный тип — это тип, который может содержать собственное значение как часть своего определения. Rust должен знать точный размер типа во время компиляции. Поскольку глубина рекурсивного типа может быть бесконечной, компилятор генерирует ошибку, если вы не используете Box (который имеет фиксированный размер, поскольку он является указателем):
enum List {
Cons(i32, Box<List>),
Nil,
}Rc<T>: подсчет ссылок
Бывают случаи, когда у одного значения может быть несколько владельцев. Интеллектуальный указатель Rc<T> (с подсчетом ссылок) отслеживает количество ссылок на значение, чтобы определить, используется ли это значение еще. Если счетчик падает до нуля, ресурс безопасно освобождается.
[!ПРИМЕЧАНИЕ]
Rc<T>подходит только для однопоточных сценариев. Для многопоточных (параллельных) сценариев необходимо использоватьArc<T>(подсчет атомарных ссылок).
use std::rc::Rc;
fn main() {
let a = Rc::new(5);
let b = Rc::clone(&a); // increments the reference count to 2
println!("References: {}", Rc::strong_count(&a));
}Попробуйте сами
Упражнение 1. Распределение кучи с помощью Box
Выделите в куче целое значение 42 с помощью Box::new и присвойте его переменной с именем val. Затем распечатайте значение, хранящееся в Box, явно разыменовав его с помощью оператора *.
Показать подсказку
Используйте `let val = Box::new(42);`, чтобы сохранить значение в куче и разыменовать его с помощью `*val` внутри `println!`.
Решение доступно после 3 попыток
Упражнение 2. Рекурсивная структура данных (список минусов)
Определите рекурсивное перечисление с именем List, содержащее два варианта: Cons, которое оборачивает кортеж (i32, Box<List>), и Nil, которое представляет конец списка. В основной функции создайте экземпляр связанного списка, содержащего элемент 1, за которым следует Nil.
Показать подсказку
Перечисление объявляется как `enum List { Cons(i32, Box<List>), Nil }`. Создайте экземпляр списка с помощью `List::Cons(1, Box::new(List::Nil))`.
Решение доступно после 3 попыток
Упражнение 3. Совместное использование владения с Rc
Используйте std::rc::Rc для создания общего целого числа, содержащего значение 100 в переменной a. Клонируйте ссылку в переменную b, используя Rc::clone. Наконец, напечатайте количество активных ссылок с помощью функции Rc::strong_count.
Показать подсказку
Создайте значение с помощью `Rc::new(100)`. Клонируйте с помощью `Rc::clone(&a)`и отобразите количество владельцев с помощью`Rc::strong_count(&a)`.
Решение доступно после 3 попыток