Exécuter du code lors du nettoyage avec le trait Drop
Le second trait important pour les pointeurs intelligents est Drop
, qui vous
permet de personnaliser ce qui se passe lorsqu'une valeur est en train de sortir
d'une portée. Vous pouvez fournir une implémentation du trait Drop
sur
n'importe quel type, et le code que vous renseignez peut être utilisé pour
libérer des ressources comme des fichiers ou des connections réseau. Nous
présentons Drop
dans le contexte des pointeurs intelligents car la
fonctionnalité du trait Drop
est quasiment systématiquement utilisée
lorsque nous implémentons un pointeur intelligent. Par exemple, lorsqu'une
Box<T>
est libérée, elle va désallouer l'espace occupé sur le tas sur lequel
la boite pointe.
Dans certains langages, le développeur doit appeler du code pour libérer la mémoire ou des ressources à chaque fois qu'il finit d'utiliser une instance ou un pointeur intelligent. S'il oublie de le faire, le système peut surcharger et planter. Avec Rust, vous pouvez renseigner du code qui sera exécuté à chaque fois qu'une valeur sort de la portée, et le compilateur va insérer automatiquement ce code. Au final, vous n'avez pas besoin de concentrer votre attention à placer du code de nettoyage à chaque fois qu'une instance d'un type particulier n'est plus utilisée — vous ne risquez pas d'avoir des fuites de ressources !
Vous renseignez le code à exécuter lorsqu'une valeur sort de la portée en
implémentant le trait Drop
. Le trait Drop
nécessite que vous implémentiez
une méthode drop
qui prend en paramètre une référence mutable à self
. Pour
voir quand Rust appelle drop
, implémentons drop
avec une instruction
println!
à l'intérieur, pour le moment.
L'encart 15-14 montre une structure PointeurPerso
dont la seule fonctionnalité
personnalisée est qu'elle va écrire Nettoyage d'un PointeurPerso !
lorsque
l'instance sort de la portée. Cet exemple signale quand Rust exécute la
fonction drop
.
Fichier : src/main.rs
struct PointeurPerso { donnee: String, } impl Drop for PointeurPerso { fn drop(&mut self) { println!("Nettoyage d'un PointeurPerso avec la donnée `{}` !", self.donnee); } } fn main() { let c = PointeurPerso { donnee: String::from("des trucs"), }; let d = PointeurPerso { donnee: String::from("d'autres trucs"), }; println!("PointeurPersos créés."); }
Le trait Drop
est importé dans l'étape préliminaire, donc nous n'avons pas
besoin de l'importer dans la portée. Nous implémentons le trait Drop
sur
PointeurPerso
et nous fournissons une implémentation de la méthode drop
qui
appelle println!
. Le corps de la fonction drop
est l'endroit où vous placez
la logique que vous souhaitez exécuter lorsqu'une instance du type concerné sort
de la portée. Ici nous affichons un petit texte pour voir quand Rust
appelle drop
.
Dans le main
, nous créons deux instances de PointeurPerso
et ensuite on
affiche PointeurPersos créés
. A la fin du main
, nos instances de
PointeurPerso
vont sortir de la portée, et Rust va appeler le code que nous
avons placé dans la méthode drop
et qui va afficher notre message final.
Notez que nous n'avons pas besoin d'appeler explicitement la méthode drop
.
Lorsque nous exécutons ce programme, nous devrions voir la sortie suivante :
$ cargo run
Compiling drop-example v0.1.0 (file:///projects/drop-example)
Finished dev [unoptimized + debuginfo] target(s) in 0.60s
Running `target/debug/drop-example`
PointeurPersos créés.
Nettoyage d'un PointeurPerso avec la donnée `d'autres trucs`!
Nettoyage d'un PointeurPerso avec la donnée `des trucs`!
Rust a appelé automatiquement drop
pour nous lorsque nos instances sont
sorties de la portée, appelant ainsi le code que nous y avions mis. Les variables
sont libérées dans l'ordre inverse de leur création, donc d
a été libéré avant
c
. Cet exemple vous fournit une illustration de la façon dont la méthode drop
fonctionne ; normalement vous devriez y mettre le code de nettoyage dont votre
type a besoin d'exécuter plutôt que d'afficher simplement un message.
Libérer prématurément une valeur avec std::mem::drop
Malheureusement, il n'est pas simple de désactiver la fonctionnalité automatique
drop
. La désactivation de drop
n'est généralement pas nécessaire ; tout
l'intérêt du trait Drop
est qu'il est pris en charge automatiquement.
Occasionnellement, cependant, vous pourriez avoir besoin de nettoyer
prématurément une valeur. Un exemple est lorsque vous utilisez des pointeurs
intelligents qui gèrent un système de verrouillage : vous pourriez vouloir
forcer la méthode drop
qui libère le verrou afin qu'un autre code dans la même
portée puisse prendre ce verrou. Rust ne vous autorise pas à 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
souhaitez forcer une valeur à être libérée avant la fin de sa portée.
Si nous essayons d'appeler manuellement la méthode drop
du trait Drop
en
modifiant la fonction main
de l'encart 15-14, comme dans l'encart 15-15, nous
aurons une erreur de compilation :
Fichier : src/main.rs
struct PointeurPerso {
donnee: String,
}
impl Drop for PointeurPerso {
fn drop(&mut self) {
println!("Nettoyage d'un PointeurPerso avec la donnée `{}` !", self.donnee);
}
}
fn main() {
let c = PointeurPerso {
donnee: String::from("des trucs"),
};
println!("PointeurPerso créé.");
c.drop();
println!("PointeurPerso libéré avant la fin du main.");
}
Lorsque nous essayons de compiler ce code, nous obtenons l'erreur suivante :
$ 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: `drop(c)`
For more information about this error, try `rustc --explain E0040`.
error: could not compile `drop-example` due to previous error
Ce message d'erreur signifie que nous ne sommes pas autorisés à appeler
explicitement drop
. Le message d'erreur utilise le terme de destructeur
(destructor
) qui est un terme général de programmation qui désigne une
fonction qui nettoie une instance. Un destructeur est analogue à un
constructeur, qui construit une instance. La fonction drop
en Rust est un
destructeur particulier.
Rust ne nous laisse pas appeler explicitement drop
car Rust appellera toujours
automatiquement drop
sur la valeur à la fin du main
. Cela serait une erreur
de double libération car Rust essayerait de nettoyer la même valeur deux fois.
Nous ne pouvons pas désactiver l'ajout automatique de drop
lorsqu'une valeur
sort de la portée, et nous ne pouvons pas désactiver explicitement la méthode
drop
. Donc, si nous avons besoin de forcer une valeur à être nettoyée
prématurément, nous pouvons utiliser la fonction std::mem::drop
.
La fonction std::mem::drop
est différente de la méthode drop
du trait
Drop
. Nous pouvons l'appeler en lui passant en argument la valeur que nous
souhaitons libérer prématurément. La fonction est présente dans l'étape
préliminaire, donc nous pouvons modifier main
de l'encart 15-15 pour appeler
la fonction drop
, comme dans l'encart 15-16 :
Fichier : src/main.rs
struct PointeurPerso { donnee: String, } impl Drop for PointeurPerso { fn drop(&mut self) { println!("Nettoyage d'un PointeurPerso avec la donnée `{}` !", self.donnee); } } fn main() { let c = PointeurPerso { donnee: String::from("des trucs"), }; println!("PointeurPerso créé."); drop(c); println!("PointeurPerso libéré avant la fin du main."); }
L'exécution de code va afficher ceci :
$ cargo run
Compiling drop-example v0.1.0 (file:///projects/drop-example)
Finished dev [unoptimized + debuginfo] target(s) in 0.73s
Running `target/debug/drop-example`
PointeurPerso créé.
Nettoyage d'un PointeurPerso avec la donnée `des trucs` !
PointeurPerso libéré avant la fin du main.
Le texte Nettoyage d'un PointeurPerso avec la donnée `des trucs` !
est affiché entre PointeurPerso créé
et
PointeurPerso libéré avant la fin du main
, ce qui démontre que la méthode
drop
a été appelée pour libérer c
à cet endroit.
Vous pouvez utiliser le code renseigné dans une implémentation du trait Drop
de plusieurs manières afin de rendre le nettoyage pratique et sûr : par exemple,
vous pouvez l'utiliser pour créer votre propre alloueur de mémoire ! Grâce au
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 toujours utilisées : le système de possession garantit que
les références restent toujours en vigueur, et garantit également que drop
n'est appelée 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, découvrons d'autres pointeurs intelligents définis dans
la bibliothèque standard.