Модульні уроки (2/2)
Розумні покажчики: Box і Rc
Розумний покажчик — це структура даних, яка поводиться як покажчик, але має метадані та додаткові можливості (наприклад, підрахунок посилань або автоматичне керування пам’яттю).
У 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: Розподіл купи за допомогою ящика
Виділіть ціле значення 42 у купі за допомогою Box::new і призначте його змінній з іменем val. Далі надрукуйте значення, що зберігається у Box, явно розіменувавши його за допомогою оператора *.
Показати підказку
Використовуйте `let val = Box::new(42);`, щоб зберегти значення в купі та розіменувати його за допомогою `*val` всередині `println!`.
Рішення доступне після 3 спроб
Вправа 2. Рекурсивна структура даних (список недоліків)
Визначте рекурсивне перелічення під назвою List, що містить два варіанти: Cons, який обгортає кортеж (i32, Box<List>), і Nil, який представляє кінець списку. У основній функції створіть екземпляр пов’язаного списку, що містить елемент 1, за яким слідує Nil.
Показати підказку
Enum оголошено як `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 спроб