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 одним з типів, які дозволяють компілятору оптимізувати зберігання деяких переліків.

Наприклад, Option<Box<T>> має такий самий розмір, як і просто Box<T>, оскільки компілятор використовує NULL-значення для розрізнення варіантів замість використання явного тегу ("Оптимізація нульового вказівника"):

use std::mem::size_of_val;

struct Item(String);

fn main() {
    let just_box: Box<Item> = Box::new(Item("Just box".into()));
    let optional_box: Option<Box<Item>> =
        Some(Box::new(Item("Optional box".into())));
    let none: Option<Box<Item>> = None;

    assert_eq!(size_of_val(&just_box), size_of_val(&optional_box));
    assert_eq!(size_of_val(&just_box), size_of_val(&none));

    println!("Розмір just_box: {}", size_of_val(&just_box));
    println!("Розмір optional_box: {}", size_of_val(&optional_box));
    println!("Розмір none: {}", size_of_val(&none));
}