Box<T>

Box — це вказівник на дані в купі:

fn main() {
    let five = Box::new(5);
    println!("five: {}", *five);
}
5StackHeapfive

Box<T> реалізує Deref<Target = T>, що означає, що ви можете викликати методи з T безпосередньо на Box<T>.

Рекурсивні типи даних або типи даних з динамічним розміром не можуть зберігатися вбудованими без перенаправлення вказівника. Box здійснює цю опосередкованість:

#[derive(Debug)]
enum List<T> {
    /// Непорожній список: перший елемент та решта списку.
    Element(T, Box<List<T>>),
    /// Порожній список.
    Nil,
}

fn main() {
    let list: List<i32> =
        List::Element(1, Box::new(List::Element(2, Box::new(List::Nil))));
    println!("{list:?}");
}
listElement1Element2NilСеткКпуа
This slide should take about 8 minutes.
  • Box схожий на std::unique_ptr у C++, за винятком того, що він гарантовано не буде null.

  • Box може бути корисним, коли ви:

    • маєте тип, розмір якого не може бути відомий під час компіляції, але компілятор Rust хоче знати точний розмір.
    • хочете передати володіння на великий обсяг даних. Щоб уникнути копіювання великих обсягів даних у стеку, натомість зберігайте дані в купі в Box, щоб переміщувався лише вказівник.
  • Якщо би Box не використовувався, і ми намагалися вставити List безпосередньо в List, компілятор не зміг би обчислити фіксований розмір структури в пам’яті (List мав би нескінченний розмір).

  • Box вирішує цю проблему, оскільки має той самий розмір, що й звичайний вказівник, і лише вказує на наступний елемент List у купі.

  • Видаліть Box у визначенні списку та відобразіть помилку компілятора. Ми отримаємо повідомлення "recursive without indirection", тому що для рекурсії даних ми повинні використовувати посередництво, Box або якесь посилання, замість того, щоб зберігати значення безпосередньо.

  • Хоча Box виглядає як std::unique_ptr у C++, він не може бути порожнім/нульовим. Це робить Box одним з типів, які дозволяють компілятору оптимізувати зберігання деяких переліків ("нішова оптимізація").