Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Executando Código na Limpeza com a Trait Drop

A segunda trait importante para o padrão de ponteiro inteligente é Drop, que permite personalizar o que acontece quando um valor está prestes a sair de escopo. Você pode fornecer uma implementação da trait Drop para qualquer tipo, e esse código pode ser usado para liberar recursos como arquivos ou conexões de rede.

Estamos introduzindo Drop no contexto de ponteiros inteligentes porque a funcionalidade da trait Drop quase sempre é usada ao implementar um ponteiro inteligente. Por exemplo, quando um Box<T> é descartado, ele desaloca o espaço no heap para o qual o box aponta.

Em algumas linguagens, para alguns tipos, a pessoa programadora precisa chamar código para liberar memória ou recursos toda vez que termina de usar uma instância desses tipos. Exemplos incluem handles de arquivos, sockets e locks. Se a pessoa esquecer, o sistema pode ficar sobrecarregado e travar. Em Rust, você pode especificar que um determinado trecho de código seja executado sempre que um valor sai de escopo, e o compilador inserirá esse código automaticamente. Como resultado, você não precisa tomar cuidado para colocar código de limpeza em todos os lugares de um programa em que uma instância de um tipo específico deixa de ser usada; ainda assim, você não vazará recursos!

Você especifica o código a ser executado quando um valor sai de escopo implementando a trait Drop. A trait Drop exige que você implemente um método chamado drop, que recebe uma referência mutável para self. Para ver quando Rust chama drop, vamos implementar drop com instruções println! por enquanto.

A Listagem 15-14 mostra uma struct CustomSmartPointer cuja única funcionalidade personalizada é imprimir Dropping CustomSmartPointer! quando a instância sai de escopo, para mostrar quando Rust executa o método drop.

Filename: src/main.rs
struct CustomSmartPointer {
    data: String,
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
    }
}

fn main() {
    let c = CustomSmartPointer {
        data: String::from("my stuff"),
    };
    let d = CustomSmartPointer {
        data: String::from("other stuff"),
    };
    println!("CustomSmartPointers created");
}
Listing 15-14: Uma struct CustomSmartPointer que implementa a trait Drop, onde colocaríamos nosso código de limpeza

A trait Drop está incluída no prelude, então não precisamos trazê-la para o escopo. Implementamos a trait Drop em CustomSmartPointer e fornecemos uma implementação para o método drop que chama println!. O corpo do método drop é onde você colocaria qualquer lógica que quisesse executar quando uma instância do seu tipo sai de escopo. Estamos imprimindo algum texto aqui para demonstrar visualmente quando Rust chamará drop.

Em main, criamos duas instâncias de CustomSmartPointer e então imprimimos CustomSmartPointers created. No final de main, nossas instâncias de CustomSmartPointer sairão de escopo, e Rust chamará o código que colocamos no método drop, imprimindo nossa mensagem final. Observe que não precisamos chamar o método drop explicitamente.

Quando executarmos esse programa, veremos a seguinte saída:

$ cargo run
   Compiling drop-example v0.1.0 (file:///projects/drop-example)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.60s
     Running `target/debug/drop-example`
CustomSmartPointers created
Dropping CustomSmartPointer with data `other stuff`!
Dropping CustomSmartPointer with data `my stuff`!

Rust chamou drop automaticamente para nós quando nossas instâncias saíram de escopo, chamando o código que especificamos. Variáveis são descartadas na ordem inversa à de sua criação, então d foi descartada antes de c. O propósito desse exemplo é dar a você um guia visual de como o método drop funciona; normalmente, você especificaria o código de limpeza que seu tipo precisa executar em vez de uma mensagem impressa.

Infelizmente, não é simples desabilitar a funcionalidade automática de drop. Desabilitar drop geralmente não é necessário; o ponto da trait Drop é que isso seja cuidado automaticamente. Ocasionalmente, porém, talvez você queira limpar um valor mais cedo. Um exemplo é ao usar ponteiros inteligentes que gerenciam locks: talvez você queira forçar o método drop que libera o lock para que outro código no mesmo escopo possa adquirir o lock. Rust não permite chamar manualmente o método drop da trait Drop; em vez disso, você precisa chamar a função std::mem::drop, fornecida pela biblioteca padrão, se quiser forçar um valor a ser descartado antes do fim de seu escopo.

Tentar chamar manualmente o método drop da trait Drop modificando a função main da Listagem 15-14 não funcionará, como mostrado na Listagem 15-15.

Filename: src/main.rs
struct CustomSmartPointer {
    data: String,
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
    }
}

fn main() {
    let c = CustomSmartPointer {
        data: String::from("some data"),
    };
    println!("CustomSmartPointer created");
    c.drop();
    println!("CustomSmartPointer dropped before the end of main");
}
Listing 15-15: Tentando chamar manualmente o método drop da trait Drop para limpar cedo

Quando tentarmos compilar esse código, receberemos este erro:

$ cargo run
   Compiling drop-example v0.1.0 (file:///projects/drop-example)
error[E0040]: explicit use of destructor method
  --> src/main.rs:16:7
   |
16 |     c.drop();
   |       ^^^^ explicit destructor calls not allowed
   |
help: consider using `drop` function
   |
16 -     c.drop();
16 +     drop(c);
   |

For more information about this error, try `rustc --explain E0040`.
error: could not compile `drop-example` (bin "drop-example") due to 1 previous error

Essa mensagem de erro afirma que não temos permissão para chamar drop explicitamente. A mensagem de erro usa o termo destructor, que é o termo geral em programação para uma função que limpa uma instância. Um destructor é análogo a um constructor, que cria uma instância. A função drop em Rust é um destrutor específico.

Rust não permite que chamemos drop explicitamente porque Rust ainda chamaria drop automaticamente no valor ao final de main. Isso causaria um erro de double free, porque Rust tentaria limpar o mesmo valor duas vezes.

Não podemos desabilitar a inserção automática de drop quando um valor sai de escopo, e não podemos chamar o método drop explicitamente. Portanto, se precisarmos forçar um valor a ser limpo mais cedo, usamos a função std::mem::drop.

A função std::mem::drop é diferente do método drop na trait Drop. Nós a chamamos passando como argumento o valor que queremos forçar a descartar. A função está no prelude, então podemos modificar main na Listagem 15-15 para chamar a função drop, como mostrado na Listagem 15-16.

Filename: src/main.rs
struct CustomSmartPointer {
    data: String,
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
    }
}

fn main() {
    let c = CustomSmartPointer {
        data: String::from("some data"),
    };
    println!("CustomSmartPointer created");
    drop(c);
    println!("CustomSmartPointer dropped before the end of main");
}
Listing 15-16: Chamando std::mem::drop para descartar explicitamente um valor antes que ele saia de escopo

Executar esse código imprimirá o seguinte:

$ cargo run
   Compiling drop-example v0.1.0 (file:///projects/drop-example)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.73s
     Running `target/debug/drop-example`
CustomSmartPointer created
Dropping CustomSmartPointer with data `some data`!
CustomSmartPointer dropped before the end of main

O texto Dropping CustomSmartPointer with data `some data`! é impresso entre o texto CustomSmartPointer created e CustomSmartPointer dropped before the end of main, mostrando que o código do método drop é chamado para descartar c naquele ponto.

Você pode usar o código especificado em uma implementação da trait Drop de várias formas para tornar a limpeza conveniente e segura: por exemplo, poderia usá-lo para criar seu próprio alocador de memória! Com a trait Drop e o sistema de ownership de Rust, você não precisa se lembrar de limpar, porque Rust faz isso automaticamente.

Você também não precisa se preocupar com problemas resultantes de limpar acidentalmente valores ainda em uso: o sistema de ownership que garante que referências sejam sempre válidas também garante que drop seja chamado apenas uma vez quando o valor não está mais sendo usado.

Agora que examinamos Box<T> e algumas das características de ponteiros inteligentes, vamos olhar para alguns outros ponteiros inteligentes definidos na biblioteca padrão.