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

Exécuter du code au nettoyage avec le trait Drop

Le deuxième trait important pour le patron des pointeurs intelligents est Drop, qui vous permet de personnaliser ce qui se passe quand une valeur est sur le point de sortir de la portée. Vous pouvez fournir une implémentation du trait Drop sur n’importe quel type, et ce code peut être utilisé pour libérer des ressources comme des fichiers ou des connexions réseau.

Nous introduisons Drop dans le contexte des pointeurs intelligents car la fonctionnalité du trait Drop est presque toujours utilisée lors de l’implémentation d’un pointeur intelligent. Par exemple, quand un Box<T> est libéré (dropped), il désallouera l’espace sur le tas vers lequel la boîte pointe.

Dans certains langages, pour certains types, le programmeur doit appeler du code pour libérer la mémoire ou les ressources chaque fois qu’il a fini d’utiliser une instance de ces types. Les exemples incluent les descripteurs de fichiers, les sockets et les verrous. Si le programmeur oublie, le système peut devenir surchargé et planter. En Rust, vous pouvez spécifier qu’un morceau de code particulier soit exécuté chaque fois qu’une valeur sort de la portée, et le compilateur insérera ce code automatiquement. En conséquence, vous n’avez pas besoin de faire attention à placer du code de nettoyage partout dans le programme quand vous avez fini avec une instance d’un type particulier – vous ne fuirez toujours pas de ressources !

Vous spécifiez le code à exécuter quand une valeur sort de la portée en implémentant le trait Drop. Le trait Drop vous demande d’implémenter une méthode nommée drop qui prend une référence mutable vers self. Pour voir quand Rust appelle drop, implémentons drop avec des instructions println! pour l’instant.

L’encart 15-14 montre une struct CustomSmartPointer dont la seule fonctionnalité personnalisée est qu’elle affichera Dropping CustomSmartPointer! quand l’instance sort de la portée, pour montrer quand Rust exécute la méthode 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: A CustomSmartPointer struct that implements the Drop trait where we would put our cleanup code

Le trait Drop est inclus dans le prélude, nous n’avons donc pas besoin de l’importer dans la portée. Nous implémentons le trait Drop sur CustomSmartPointer et fournissons une implémentation de la méthode drop qui appelle println!. Le corps de la méthode drop est l’endroit où vous placeriez toute logique que vous souhaitez exécuter quand une instance de votre type sort de la portée. Nous affichons du texte ici pour démontrer visuellement quand Rust appellera drop.

Dans main, nous créons deux instances de CustomSmartPointer puis affichons CustomSmartPointers created. À la fin de main, nos instances de CustomSmartPointer sortiront de la portée, et Rust appellera le code que nous avons mis dans la méthode drop, affichant notre message final. Notez que nous n’avons pas eu besoin d’appeler la méthode drop explicitement.

Lorsque nous exécutons ce programme, nous verrons la sortie suivante : console {{#include ../listings/ch15-smart-pointers/listing-15-14/output.txt}}

$ 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 a automatiquement appelé drop pour nous quand nos instances sont sorties de la portée, en appelant le code que nous avons spécifié. Les variables sont libérées dans l’ordre inverse de leur création, donc d a été libéré avant c. Le but de cet exemple est de vous donner un guide visuel de comment la méthode drop fonctionne ; habituellement, vous spécifieriez le code de nettoyage que votre type doit exécuter plutôt qu’un message d’affichage.

Malheureusement, il n’est pas simple de désactiver la fonctionnalité automatique de drop. Désactiver drop n’est généralement pas nécessaire ; tout l’intérêt du trait Drop est qu’il est géré automatiquement. Cependant, il arrive parfois que vous souhaitiez nettoyer une valeur plus tôt. Un exemple est lorsque vous utilisez des pointeurs intelligents qui gèrent des verrous : vous pourriez vouloir forcer la méthode drop qui libère le verrou afin que d’autre code dans la même portée puisse acquérir le verrou. Rust ne vous permet pas d’appeler manuellement la méthode drop du trait Drop ; à la place, vous devez appeler la fonction std::mem::drop fournie par la bibliothèque standard si vous voulez forcer la libération d’une valeur avant la fin de sa portée.

Essayer d’appeler manuellement la méthode drop du trait Drop en modifiant la fonction main de l’encart 15-14 ne fonctionnera pas, comme montré dans l’encart 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: Attempting to call the drop method from the Drop trait manually to clean up early

Lorsque nous essayons de compiler ce code, nous obtenons cette erreur : console {{#include ../listings/ch15-smart-pointers/listing-15-15/output.txt}}

$ 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

Ce message d’erreur indique que nous ne sommes pas autorisés à appeler explicitement drop. Le message d’erreur utilise le terme destructeur, qui est le terme de programmation générale pour une fonction qui nettoie une instance. Un destructeur est l’analogue d’un constructeur, qui crée une instance. La fonction drop en Rust est un destructeur particulier.

Rust ne nous permet pas d’appeler drop explicitement, car Rust appellerait quand même automatiquement drop sur la valeur à la fin de main. Cela provoquerait une erreur de double libération car Rust essaierait de nettoyer la même valeur deux fois.

Nous ne pouvons pas désactiver l’insertion automatique de drop quand une valeur sort de la portée, et nous ne pouvons pas appeler la méthode drop explicitement. Donc, si nous avons besoin de forcer le nettoyage anticipé d’une valeur, nous utilisons la fonction std::mem::drop.

La fonction std::mem::drop est différente de la méthode drop du trait Drop. Nous l’appelons en passant comme argument la valeur que nous voulons forcer à libérer. La fonction est dans le prélude, donc nous pouvons modifier main dans l’encart 15-15 pour appeler la fonction drop, comme montré dans l’encart 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: Calling std::mem::drop to explicitly drop a value before it goes out of scope

L’exécution de ce code affichera ce qui suit : console {{#include ../listings/ch15-smart-pointers/listing-15-16/output.txt}}

$ 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

Le texte Dropping CustomSmartPointer with data `some data`! est affiché entre les textes CustomSmartPointer created et CustomSmartPointer dropped before the end of main, montrant que le code de la méthode drop est appelé pour libérer c à ce moment-là.

Vous pouvez utiliser le code spécifié dans une implémentation du trait Drop de nombreuses manières pour rendre le nettoyage pratique et sûr : par exemple, vous pourriez l’utiliser pour créer votre propre allocateur de mémoire ! Avec le trait Drop et le système de possession de Rust, vous n’avez pas à vous souvenir de nettoyer, car Rust le fait automatiquement.

Vous n’avez pas non plus à vous soucier des problèmes résultant du nettoyage accidentel de valeurs encore utilisées : le système de possession qui s’assuré que les références sont toujours valides garantit également que drop n’est appelé qu’une seule fois lorsque la valeur n’est plus utilisée.

Maintenant que nous avons examiné Box<T> et certaines des caractéristiques des pointeurs intelligents, examinons quelques autres pointeurs intelligents définis dans la bibliothèque standard.