FAQ sur la programmation en langage RustConsultez toutes les FAQ
Nombre d'auteurs : 1, nombre de questions : 95, dernière mise à jour : 21 juillet 2018 Ajouter une question
Cette FAQ a été réalisée pour répondre aux questions les plus fréquemment posées sur la programmation en langage Rust. Nous vous recommandons de la consulter avant de poser vos questions sur le forum Rust.
Nous tenons à souligner que cette FAQ ne garantit en aucun cas que les informations qu'elle contient sont correctes ; les auteurs font le maximum, mais l'erreur est humaine. Si vous trouvez une erreur, ou que vous souhaitez devenir rédacteur, lisez ceci.
Toute nouvelle question/réponse est la bienvenue, vous pouvez proposer vos questions réponses par e-mail (voir le lien en bas de page) au(x) responsable(s) de la rubrique Rust ou mieux en postant votre proposition à la publication.
Vous pouvez participer à l'amélioration des questions réponses en postant directement dans cette FAQ. Une validation par le(s) responsable(s) de la rubrique Rust sera requise avant que vos contributions ne soient visibles.
Sur ce, nous vous souhaitons une bonne lecture.
Cette FAQ dispose de trois Q/R abordant trois concepts distincts (mais se complétant) gravitant autour de la gestion de la mémoire avec le langage Rust.
Par souci de concision, les Q/R ci-dessous ne retiennent que l'essentiel de chaque concept :
Cette Q/R abordant un concept propre au langage Rust, certains points pourraient encore vous paraître obscures. Si c'est le cas, vous pouvez vous reporter directement à la section, dédiée à ce sujet, de la documentation officielle du langage.
Nous verrons un peu plus bas que le fonctionnement de ce mécanisme n'est pas si étranger au sens littéral du terme.
Introduction
Rust est muni d'un système « d'appartenance » qui permet d'écarter les conflits les plus communs lorsqu'une ressource est utilisée à plusieurs endroits.
Bien que ce dernier soit très pratique, il demande d'avoir une certaine rigueur quant à la déclaration de nos ressources, sans quoi vous risqueriez de vous attirer les foudres du compilateur.
Pour cela, voici un exemple d'erreur typique lorsque l'on débute sans réellement connaître les tâches effectuées par le « ramasse-miette » :
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 | fn main() { let foo : String = String::from("Hello world!"); let bar : String = foo; let baz : String = foo; //erreur la ressource a été « déplacée » } |
Renvoyant une erreur de ce style :
Code : | Sélectionner tout |
error: use of moved value: `foo`
C'est simple :
La variable foo étant un pointeur contenant l'adresse mémoire d'un objet String, il est courant de dire qu'il possède « l'ownership », il est le seul à pouvoir utiliser cette ressource.
C'est en copiant les informations relatives à l'objet String (en « déplaçant » ces informations dans une nouvelle variable, donc) que le garbage collector va faire son travail : détruire le pointeur foo pour attribuer « l'ownership » au nouveau pointeur de la ressource : bar.
C'est lorsque la variable baz essaie de copier les informations de foo que l'erreur survient : foo a déjà été détruit par le garbage collector.
Pour remédier au problème, il aurait suffi de copier bar de cette manière :
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 | fn main() { let foo: String = "Hello world!".to_owned(); let bar: String = foo; let bar2: String = bar.clone(); // on clone bar let baz: &String = &bar; // on récupère une référence } |
Tout est en règle, le compilateur ne râle plus, et si vous souhaitez afficher votre chaîne de caractères sur la sortie standard, rien ne vous en empêche !
Vous pouvez très bien écrire ceci :
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 | fn main() { let foo = 42; let bar = foo; let baz = foo; } |
Car les types primitifs tels que les i8, i16, i32, i64, u8, ... implémentent le trait Copy.
Il est cependant important de noter que les appels à clone sont très coûteux et ne devraient être utilisés qu'en derniers recours. En général (sauf si vous souhaitez vraiment copier les données), on peut toujours faire autrement.
Quid des fonctions ?
Les fonctions obéissent aux mêmes règles que les pointeurs :
Lorsqu'une ressource est passée en paramètre par copie, la fonction « possède » la ressource, même lorsqu'elle a terminé de s'exécuter.
Si la ressource en question a été créée dynamiquement, elle sera systématiquement détruite lorsque la fonction aura terminé de s'exécuter ; sinon, elle devient simplement inaccessible pour le restant de l'exécution.
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | fn my_func(my_string: String) { let chars = my_string.chars(); for letter in chars { println!("{}", &letter); } } fn main() { let foo : String = String::from("The cake is a lie!"); my_func(foo); let chars = foo.chars(); //error } |
Vous remarquerez donc ici que le pointeur foo a été détruit, la copie de la chaîne de caractères appartient désormais à la fonction.
Ne vous attardez pas sur le contenu de la fonction myfunc() ;
Ce n'est qu'un exemple parmi tant d'autres, gardez simplement à l'esprit que si la ressource est passée en paramètre par copie, le pointeur vers cette dernière est détruit.
Cette Q/R abordant un concept propre au langage Rust, certains points pourraient encore vous paraître obscures. Si c'est le cas, vous pouvez vous reporter directement à la section, dédiée à ce sujet, de la documentation officielle du langage.
Rust remédie à ce problème grâce au « borrow checking », un système d'emprunts créant en quelque sorte des mutex chargés de limiter l'accès à une ressource et ainsi éviter les risques d'écriture simultanés.
Le borrow checker fera respecter ces trois règles (que vous pouvez retrouver dans la documentation officielle) :
- Une (ou plusieurs) variable peut emprunter la ressource en lecture. (référence immuable) ;
- Un seul, et seulement un, pointeur doit disposer d'un accès en écriture sur la ressource ;
- Vous ne pouvez pas accéder à la ressource en lecture et en écriture en même temps, exemple :
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 | fn main() { let mut foo = 117; let bar = &mut foo; let baz = &foo; //erreur } |
Ou :
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 | fn main() { let mut foo = 117; let bar = &mut foo; let baz = &mut foo; //erreur } |
Cette Q/R abordant un concept propre au langage Rust, certains points pourraient encore vous paraître obscures. Si c'est le cas, vous pouvez vous reporter directement à la section, dédiée à ce sujet, de la documentation officielle du langage.
Comme tous langages (sauf exception que nous pourrions ignorer), Rust dispose d'un système de durée de vie.
Toutefois, il fait preuve d'une grande rigueur quant à la destruction des ressources dynamiques et à « l'isolement » des ressources statiques après utilisation.
Voici un exemple :
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 | fn main() { let mut foo : String = "Hello world!".to_string(); //Le scope A commence ici let bar : String = "Goodbye, friend !".to_string();//Le scope B commence ici foo = bar;// bar détruit, le scope B s'arrête là println!("{}", &bar); // erreur du compilateur } // Le Scope A s'arrête ici |
On remarque à la suite de cet exemple que le concept de « scope » (contexte) n'est pas à l'échelle d'une fonction, mais bien des variables, incitant le développeur à déclarer et initialiser sa ressource uniquement lorsqu'il en a besoin.
Quid des références ?
Le concept de durée de vie dédiée aux références peut parfois dérouter, surtout lorsqu'il faut expliciter certains tags (représentants des durées de vie) au compilateur lorsqu'il nous l'impose et que l'on ne comprend pas bien pourquoi.
Les références n'échappent pas à la règle, elles aussi ont des durées de vie bien déterminées ; en règle générale, il n'est pas utile (voire interdit) au développeur d'expliciter les tags qui permettent au compilateur de « suivre » chaque référence durant son utilisation.
Cependant, lorsque l'une d'elles est passée en paramètre à une fonction, il peut parfois être nécessaire de tagger celles qui survivront au moins à l'exécution de la fonction (ne serait-ce que par souci de clareté).
Voici un exemple qui pourrait vous épauler : (attention à bien lire les commentaires)
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | fn foo(phrase: &str) -> () //aucune référence ne survit, donc pas la peine de l'annoter { println!("{}", &phrase); } fn bar<'a>(phrase: &'a mut String, word: &str) -> &'a String //une référence va survivre il faut maintenant savoir laquelle { phrase.push_str(word); return phrase; }//La référence qui survivra sera donc « phrase », elle dispose donc de la durée de vie 'a. fn main() { let mut baz : String = "Hello ".to_string(); let word : &str = "world!"; let bazz = bar(&mut baz, word); //ce que contient la variable bazz ne peut être accédé qu'en lecture println!("{}", &bazz); //nous affichons nos caractères sur la sortie standard } |
En revanche, ce n'est pas un cas commun, nous vous invitons donc à vous tourner vers la documentation officielle ou à expérimenter par vous-même.
Que faut-il retenir ?
Pour faire simple, il faut retenir que :
- Chaque variable crée un nouveau scope lors de sa déclaration ;
- Toute variable retrouvée dans le scope d'une autre verra sa durée de vie plus courte que cette dernière ;
- À propos des références passées en paramètres, seules les références survivant au moins jusqu'à la fin de l'exécution de la fonction devraient être annotées.
Proposer une nouvelle réponse sur la FAQ
Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour çaLes sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2024 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.