Умные указатели и что дальше
Обзорно знакомимся с умными указателями Box и Rc и намечаем путь дальнейшего изучения Rust.
Умный указатель — тип, который ведёт себя как ссылка, но несёт дополнительные возможности владения и управления памятью.
Box — значение в куче
Обычно Rust размещает значения на стеке. Box<T> кладёт значение в кучу, оставляя на стеке лишь указатель. Это нужно, когда размер неизвестен на этапе компиляции — например, для рекурсивных структур данных, которые иначе были бы «бесконечного» размера.
// рекурсивный список: каждый узел владеет следующим через Box
enum List {
Cons(i32, Box<List>), // Box даёт известный размер указателя
Nil,
}
use List::{Cons, Nil};
fn main() {
let list = Cons(1, Box::new(Cons(2, Box::new(Nil))));
// обходим список
let mut node = &list;
while let Cons(value, next) = node {
println!("{value}");
node = next;
}
}Вывод:
1 2
Без Box компилятор не смог бы вычислить размер List: он содержал бы сам себя бесконечно. Box разрывает рекурсию, ведь размер указателя известен и фиксирован.
Rc — несколько владельцев
Правило «один владелец» иногда мешает: бывает, что одни данные должны принадлежать нескольким местам сразу, например узел графа, на который ссылаются разные узлы. Для этого есть Rc<T> (reference counted) — он ведёт счётчик владельцев и освобождает данные, когда последний владелец исчезает.
use std::rc::Rc;
fn main() {
let a = Rc::new(String::from("общие данные"));
let b = Rc::clone(&a); // не копия данных, а ещё один владелец (+1 к счётчику)
let c = Rc::clone(&a);
println!("{}", a);
println!("владельцев: {}", Rc::strong_count(&a)); // 3
let _ = (b, c);
}Вывод:
общие данные владельцев: 3
Rc::clone не копирует сами данные — он лишь увеличивает счётчик ссылок. Это дёшево. Rc работает в однопоточном коде; для многопоточного есть его потокобезопасный аналог Arc.
| Указатель | Зачем |
Box<T> | значение в куче, рекурсивные типы, один владелец |
Rc<T> | несколько владельцев в одном потоке (счётчик ссылок) |
Arc<T> | несколько владельцев в многопоточном коде |
Что изучать дальше
Вы освоили фундамент Rust. Следующие большие темы:
- Конкурентность. Потоки, каналы,
ArcиMutex. Главное обещание Rust здесь — «бесстрашная многопоточность»: borrow checker не пропускает гонки данных и в конкурентном коде. - Асинхронность.
async/awaitи рантаймы вроде Tokio для тысяч одновременных задач — основа сетевых сервисов. - Обработка ошибок в реальных проектах. Крейты
thiserrorиanyhowупрощают работу сResult. - Трейт-объекты и динамическая диспетчеризация (
dyn Trait) для гетерогенных коллекций.
Главное вы уже умеете: думать в терминах владения и заимствования. Когда компилятор ругается, спрашивайте себя «кто владеет этими данными и как долго живёт ссылка» — и ответ почти всегда найдётся.
Итог
Box<T>размещает значение в куче; нужен для рекурсивных типов.Rc<T>даёт нескольких владельцев через счётчик ссылок (Arc— для потоков).- Дальше: конкурентность, async/await, трейт-объекты — но фундамент владения у вас уже есть.