Box<T>

Box Ă© um ponteiro owned para dados no heap:

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

Box<T> implementa Deref<Target = T>, o que significa que vocĂȘ pode chamar mĂ©todos de T diretamente em um Box<T>.

Tipos de dados recursivos ou tipos de dados com tamanhos dinĂąmicos precisam usar uma Box:

#[derive(Debug)]
enum List<T> {
    /// Uma lista nĂŁo vazia: primeiro elemento e o resto da lista.
    Element(T, Box<List<T>>),
    /// Uma lista vazia.
    Nil,
}

fn main() {
    let list: List<i32> =
        List::Element(1, Box::new(List::Element(2, Box::new(List::Nil))));
    println!("{list:?}");
}
PilhaHeaplistaElem1Elem2Nil
This slide should take about 8 minutes.
  • Box Ă© parecido com std::unique_ptr em C++, exceto que ele Ă© garantidamente nĂŁo nulo.

  • Uma Box Ă© Ăștil quando vocĂȘ:

    • hĂĄ um tipo cujo tamanho nĂŁo estĂĄ disponĂ­vel em tempo de compilação, mas o compilador Rust precisa saber o tamanho exato.
    • quer transferir o ownership de um grande volume de dados. Ao invĂ©s de copiar grandes volumes de dados na pilha, eles sĂŁo armazenados usando uma Box no heap e apenas o ponteiro Ă© movido.
  • Se Box nĂŁo for usado e tentarmos incorporar um List diretamente no List, o compilador nĂŁo conseguiria calcular um tamanho fixo da struct na memĂłria (List teria tamanho infinito).

  • Box resolve esse problema, pois tem o mesmo tamanho de um ponteiro normal e apenas aponta para o prĂłximo elemento da List no heap.

  • Remova o Box na definição de List e mostre o erro de compilação. NĂłs obtemos a mensagem "recursive without indirection" (recursivo sem indireção), porque para recursĂŁo de dados, temos que usar indireção, um Box ou referĂȘncia de algum tipo, ao invĂ©s de armazenar o valor diretamente.

Mais para Explorar

Otimização de Nicho

Embora Box pareça com std::unique_ptr em C++, ele não pode ser vazio/nulo. Isso faz com que Box seja um dos tipos que permitem ao compilador otimizar o armazenamento de alguns enums.

Por exemplo, Option<Box<T>> tem o mesmo tamanho que apenas Box<T>, porque o compilador usa o valor NULL para discriminar as variantes em vez de usar uma tag explĂ­cita ("Null Pointer Optimization"):

use std::mem::size_of_val;

struct Item(String);

fn main() {
    let just_box: Box<Item> = Box::new(Item("Apenas box".into()));
    let optional_box: Option<Box<Item>> =
        Some(Box::new(Item("Box opcional".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!("Tamanho de just_box: {}", size_of_val(&just_box));
    println!("Tamanho de optional_box: {}", size_of_val(&optional_box));
    println!("Tamanho de none: {}", size_of_val(&none));
}