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.
- Comment déclarer une variable ?
- Comment assigner un objet par référence ?
- Rust possède-t-il un typage dynamique ?
- Comment typer ses données/variables ?
- Quelle est la différence entre &str et String ?
- Comment créer une chaîne de caractères ?
- Quelle version de Rust est recommandée ?
- Rust est-il orienté objet ?
- Qu'est-ce qu'un « trait » ?
- Rust supporte-t-il la surchage des fonctions ?
- Comment déclarer des paramètres optionnels ?
- Comment créer un tableau ?
- À quoi sert le mot-clé super ?
- À quoi sert le mot-clé self ?
- À quoi sert le mot-clé use ?
- À quoi sert le mot-clé pub ?
- À quoi servent les mot-clés extern crate ?
- À quoi sert le mot-clé mod ?
- À quoi sert un module ?
- Comment créer un module ?
- À quoi sert le mot-clé type ?
- À quoi sert le mot-clé loop ?
- À quoi sert le mot-clé where ?
- À quoi sert le mot-clé unsafe ?
- Quelles sont les règles non appliquées dans ces contextes ?
- Quels comportements sont considérés « non sûrs » par Rust ?
- À quoi sert le mot-clé fn ?
- À quoi sert le mot-clé match ?
- À quoi sert le mot-clé ref ?
- À quoi sert le mot-clé mut ?
- Une erreur survient lorsque je modifie le contenu de ma variable ! Que faire ?
- Qu'est-ce qu'une macro ?
- Comment créer une macro ?
- Que sont les spécificateurs ?
- À quoi sert le mot-clé usize ?
- À quoi sert le mot-clé isize ?
- Existe-t-il des outils de build pour le langage Rust ?
- Comment utiliser mes fonctions en dehors de mon module ?
- Comment comparer deux objets avec Rust ?
- Qu'est-ce que le shadowing ?
- Qu'est-ce que la déstructuration ?
- Comment effectuer une déstructuration sur une liste ?
- Comment effectuer une déstructuration sur une énumération ?
- Comment effectuer une déstructuration sur une structure ?
- Comment comparer deux objets d'une structure personnalisée avec Rust ?
- Je n'arrive pas à utiliser les macros importées par ma bibliothèque ! Pourquoi ?
- À quoi servent les mot-clés if let ?
- À quoi servent les mot-clés while let ?
- À quoi sert le 'b' qui préfixe la chaîne de caractères ?
- Qu'est-ce qu'un tuple ?
La déclaration d'une variable en Rust se fait par le biais du mot-clé let, permettant ainsi de différencier une assignation d'une expression.
Vous pouvez bien entendu déclarer et initialiser plusieurs variables en même temps de cette manière :
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | fn main() { let (foo, bar, baz) = (117, 42, "Hello world!"); // ou let foo = 117; let bar = 42; let baz = "Hello world!"; } |
Il existe deux façons de faire :
- Préciser par le biais du caractère & (C-style).
- En utilisant le mot-clé ref comme ceci :
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 | fn main() { let foo: i32 = 117; let ref bar = foo; let baz = &foo; //idem } |
Non.
Bien qu'il en donne l'air grâce à une syntaxe très aérée, Rust dispose d'un typage statique mais « optionnel » pour le développeur si ce dernier désire faire abstraction des types. Cependant il perdra, en toute logique, l'avantage de choisir la quantité de mémoire que sa ressource consommera.
Par exemple, vous ne pouvez pas faire ceci :
Code Rust : | Sélectionner tout |
1 2 3 4 | fn main() { let mut foo = 1; foo = "Hello world !"; } |
Le type ayant été fixé par la première donnée, il n'est plus possible de changer en cours de route.
Pour les types primitifs, il existe deux manières de typer une variable :
Code Rust : | Sélectionner tout |
1 2 3 4 | fn main() { let foo: i32 = 117; } |
Code Rust : | Sélectionner tout |
1 2 3 4 | fn main() { let bar = 117i32; } |
&str est un type immuable représentant une chaîne de caractères tandis que String est un wrapper mutable au-dessus de ce dernier.
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 | fn main() { let foo: &str = "Hello world!"; // ça fonctionne let bar: String = foo; // erreur let baz: String = foo.to_owned(); // Ok ! let baz: String = foo.to_string(); // Ok ! (équivalent de la ligne du dessus) } |
La question pourrait paraître évidente dans d'autres langages, toutefois, après avoir écrit quelque chose de ce style :
Code Rust : | Sélectionner tout |
1 2 3 | fn main() { let foo: String = "Hello world!"; } |
Le compilateur vous a renvoyé cette erreur :
Code : | Sélectionner tout |
1 2 3 | |> 4 |> let foo: String = "Hello world!"; |> ^^^^^^^^^^^^^^ expected struct `std::string::String`, found &-ptr |
Vous vous retrouvez donc à typer votre variable pour accueillir une instance de la structure String alors que vous créez une chaîne de caractères primitive.
Pour remédier au problème (si vous souhaitez malgré tout utiliser le wrapper), vous pouvez convertir une chaîne de caractères de type &str grâce à la fonction String::from() :
Code Rust : | Sélectionner tout |
1 2 3 4 5 | fn main() { let foo: String = String::from("Hello world!"); // ou let foo: &str = "Hello world!"; } |
Ou encore avec les méthodes to_owned et to_string (à préférer à la méthode from qui est un peu plus générale et donc plus lente) :
Code Rust : | Sélectionner tout |
1 2 3 4 | fn main() { let foo = "Hello world!".to_owned(); let foo = "Hello world!".to_string(); } |
Actuellement(1er février 2017), la version stable la plus récente est la 1.14.0.
Les versions antérieures à la 1.13.0 contenant des régressions, je vous conseille d'utiliser la version la plus récente proposée.
Page officielle du langage Rust
Rust propose l'encapsulation qui est un concept objet. On peut donc dire que Rust est orienté objet. Toutefois, l'encapsulation s'effectue à l'échelle d'un module et non d'une classe/structure comme on pourrait le remarquer en Java/C#.
Il dispose d'un aspect de la POO, de prime abord, assez primitif; Rust permet de bénéficier du polymorphisme grâce aux « traits » qui pourraient être comparées aux interfaces Java/C#.
Cependant, le langage ne supporte pas l'héritage multiple (ni l'héritage simple) entre les structures, comme il serait possible de le faire avec des classes, bien qu'il soit possible de le faire avec des traits.
Par conséquent, Rust est orienté objet, puisqu'il possède plusieurs parties de ce paradigme, mais n'est pas un langage objet pur.
Un trait pourrait être comparé aux interfaces que l'on peut retrouver dans la plupart des langages orientés objet (Java, C#…).
Les traits vous permettent de déclarer des fonctions abstraites/virtuelles pour ensuite les implémenter au sein d'une structure grâce au mot-clé impl comme ceci :
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | trait Greeter { fn greetings(&self); } struct Person; impl Greeter for Person { fn greetings(&self) { println!("Hello, my friends!"); } } fn main() { let person = Person; person.greetings(); } |
Pour aller au plus simple, un trait vous permet d'écrire un ensemble de fonctions qu'un objet est obligé d'implémenter lorsqu'il hérite de ce trait.
À quoi sert le mot-clé self ?
Rust ne supporte pas la surcharge des fonctions.
Le langage repose sur le « Builder Pattern » qui consiste à concevoir des « fabriques/factories » chargées de générer l'objet désiré.
Vous pouvez retrouver quelques explications à propos de ce design pattern ici ou encore ici.
Comment déclarer des paramètres optionnels ?
Il n'est pas possible de déclarer des paramètres optionnels avec Rust dans sa version actuelle.
Toutefois, il est toujours possible d'user de macros pour capturer différentes expressions et ainsi adapter votre code en fonction de la situation.
Le langage repose sur le « Builder Pattern » qui consiste à concevoir des « fabriques/factories » chargées de générer l'objet désiré.
Vous pouvez retrouver quelques explications à propos de ce design pattern ici ou encore ici.
Comment créer une macro ?
Un tableau dans sa forme la plus primitive se déclare comme ceci :
Code Rust : | Sélectionner tout |
let foo: [i32; 10] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
Note: la taille du tableau doit être explicite, sous peine de recevoir une erreur de la part du compilateur.
Contrairement à ce que l'on pourrait croire, le mot-clé super ne représente pas une référence vers l'instance courante d'une classe mère, mais représente seulement le « scope » supérieur (dans un module).
Exemple :
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | mod mon_module { pub fn ma_fonction() { println!("Scope supérieur"); } pub mod fils { pub fn fonction_enfant() { super::ma_fonction(); } } pub mod fille { pub fn fonction_enfant() { super::ma_fonction(); } } } fn main() { mon_module::fils::fonction_enfant(); mon_module::fille::fonction_enfant(); } |
Le mot-clé self est l'instance courante de votre type.
Il est souvent rencontré :
- lorsqu'une fonction virtuelle/abstraite est implémentée au sein d'une structure (et qu'elle doit être dépendante d'une instance de cette dernière) ;
- lorsque le développeur doit utiliser une fonction dans le module courant ;
- …
Exemple:
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | trait My_Trait { fn my_func(&self); } mod My_Mod { fn foo() { self::bar(); } fn bar() { } } |
Il sert aussi à désigner le module courant lors d'un import. Par exemple :
Code Rust : | Sélectionner tout |
1 2 3 | use std::io::{self, File}; // maintenant on peut utiliser File mais aussi io ! |
Le mot-clé use permet de gérer les imports d'autres modules.
Exemple :
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 8 | extern crate mon_package; use mon_package::mon_module::ma_fonction; fn main() { ma_fonction(); } |
Autrement dit, toute structure composée de différentes ressources peut être exploitée par le mot-clé use.
Exemple :
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 | enum MonEnum { Arg1, Arg2, } fn main() { use MonEnum::Arg1; // On importe le variant Arg1 de l'enum MonEnum ici. let instance = Arg1; // Et on l'utilise directement ici. // Plus la peine d'expliciter d'où provient l'instance Arg1 comme ceci : let instance = MonEnum::Arg1; } |
Il permet aussi de réexporter des modules vers le scope supérieur. Prenons par exemple un projet possédant cette hiérarchie :
Code text : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | src ├─── fichier.rs ├─── video | ├──── video.rs | ├──── mod.rs | ├─── audio ├──── audio.rs ├──── mod.rs |
Pour pouvoir accéder aux items présents dans audio.rs et video.rs, vous allez devoir les rendre visibles dans les niveaux supérieurs en les réexportant comme ceci :
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | // dans video/mod.rs pub use self::video::{Video, une_fonction}; mod video; // dans audio/mod.rs pub use self::audio::{Audio, une_autre_fonction}; mod audio; |
Dans fichier.rs, vous pourrez désormais faire :
Code Rust : | Sélectionner tout |
1 2 | use Audio; use Video; |
Le mot-clé pub peut être utilisé dans trois contextes différents :
1. Au sein [et sur] des modules;
2. Au sein [et sur] des traits;
3. Au sein [et sur] des structures.
Dans un premier temps, qu'il soit utilisé sur des modules, traits, ou structures, il aura toujours la même fonction : rendre publique l'objet concerné.
Exemple :
Code text : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 | ├── Cargo.lock ├── Cargo.toml ├── src │ ├── lib.rs │ └── main.rs └── target └── debug ├── build ├── deps ├── examples ├── libmon_projet.rlib ├── mon_projet └── native |
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 | pub mod ma_lib { //la module représentant ma bibliothèque pub mod mon_module { // un module lambda pub fn ma_fonction() { //ma fonction println!("Hi there !"); } } } |
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 | extern crate mon_projet; use mon_projet::ma_lib::mon_module::ma_fonction; fn main() { ma_fonction(); } |
Renvoie :
Code text : | Sélectionner tout |
Hi there !
mon_projet est le nom porté par votre projet dans le manifest Cargo.toml.
Pour cet exemple, voici le manifest rédigé :
Code toml : | Sélectionner tout |
1 2 3 4 5 6 | [package] name = "mon_projet" version = "0.1.0" authors = ["Songbird0 <chaacygg@gmail.com>"] [dependencies] |
Comment faire une méthode statique ?
Tout dépend de la présence de self/&self/&mut self en premier argument. Exemple :
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | struct A; impl A { fn foo() { // ceci est une méthode statique } fn foo1(arg: i32) { // ceci est une méthode statique } fn foo2(&self) { // ceci n'est pas une méthode statique } fn foo3(self) { // ceci n'est pas une méthode statique non plus } fn foo4(&self, arg: i32) { // ceci n'est pas non plus une méthode statique } } |
Les mot-clés extern crate permettent d'importer un paquet entier de modules dans le fichier courant.
Le principe est simple, il vous suffit de créer en premier lieu un projet en mode « bibliothèque » pour réunir tous les modules que vous créerez, de créer un fichier qui accueillera le point d'entrée de votre programme, puis d'importer votre paquet.
Bien entendu, si vous souhaitez importer un paquet qui n'est pas de vous, il vous faudra l'inscrire dans votre manifest.
Pour voir un exemple de création de paquet, vous pouvez vous rendre à la Q/R : « A quoi sert le mot-clé pub ? »
Comment installer de nouvelles bibliothèques ?
Le mot-clé mod vous permet d'importer ou de déclarer un module. Il est important de noter que les fichiers sont considérés comme des modules. Exemple :
Code Rust : | Sélectionner tout |
1 2 3 4 5 | mod a { // Ici on crée un module. fn foo() {} } mod nom_du_fichier; // Ici on importe le fichier "nom_du_fichier.rs". |
A quoi sert un module ?
Il vous permet de réunir plusieurs objets (structures, traits, fonctions, d'autres modules…) dans un même fichier puis de les réutiliser à plusieurs endroits dans votre programme.
Voici comment créer un module :
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | mod A { fn votre_fonction() {} fn une_autre_fonction() {} mod B { struct C; trait D {} } } |
Le mot-clé type permet de créer des alias et ainsi réduire la taille des types personnalisés (ou primitifs).
Voici un exemple :
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 | use std::collections::HashMap; type MonDico = HashMap<String, i32>; // On met un alias sur la HashMap. fn main() { let x = MonDico::new(); // Plus besoin de s'embêter avec le long nom et ses paramètres de types ! } |
Retrouvez des explications ici.
Explications de la documentation officielle.
Le mot-clé loop sert à créer des boucles infinies. Pour faire simple, c'est un sucre syntaxique qui permet de remplacer le fameux :
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | while(true) { } // ou for(;;) { } |
Préférez donc cette syntaxe :
Code Rust : | Sélectionner tout |
1 2 3 | loop { } |
Le mot-clé where permet de filtrer les objets passés en paramètres dans une fonction générique, par exemple :
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | trait Soldier{} trait Citizen{} struct A; struct B; impl Soldier for A{} fn foo<T>(test: T) -> T where T: Soldier { return test; } fn main() { let soldier : A = A; let citizen : B = B; foo(soldier); foo(citizen); //error: the trait bound `B: Soldier` is not satisfied } |
Le mot-clé unsafe permet, comme son nom l'indique, de casser certaines règles natives de Rust pour effectuer des opérations « à risque ».
En pratique, le mot-clé unsafe permet une manipulation de la mémoire plus approfondie, plus directe, mais aussi plus compliquée, puisque le langage n'applique pas certaines règles.
Pour faire simple : utilisez unsafe aussi peu que possible.
Exemple d'utilisation d'unsafe :
Code Rust : | Sélectionner tout |
1 2 3 | let x: i32 = &0; let ptr = x as *const i32; unsafe { *ptr; } // on tente d'accéder à l'élément pointé par le pointeur, ce qui est hautement "unsafe" |
Trois règles, et seulement trois, sont brisées dans les blocs (et fonctions) unsafe :
- L'accès et la modification d'une variable globale (statique) muable sont autorisés ;
- Il est possible de déréférencer un pointeur (non nul, donc) ;
- Il est possible de faire à une fonction non sûre.
Pour en retrouver une liste exhaustive, rendez-vous à la section dédiée.
En Rust, pour déclarer une fonction, il faut utiliser le mot-clé fn :
Code Rust : | Sélectionner tout |
1 2 3 4 | fn ma_fonction() { } |
Pour spécifier le type renvoyé par la fonction, vous devez utiliser le token -> + le type de votre choix.
Code Rust : | Sélectionner tout |
1 2 3 4 | fn ma_fonction() -> Foo { Foo } |
Le mot-clé match nous permet d'implémenter le pattern matching.
Ainsi, il est possible de comparer une entrée à plusieurs tokens constants et agir en conséquence. Le pattern matching est considéré comme un test exhaustif, car, quoi qu'il arrive, il fera en sorte de couvrir tous les cas de figure qu'on lui propose.
Exemple :
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 | let foo: i32 = 117; match foo { 117 => println!("foo vaut 117 !"), _ => println!("You know nothing, John."), // s'efforcera de trouver une réponse } |
Jusqu'ici, il semblerait que le mot-clé match ne soit pas capable de faire preuve de plus de souplesse qu'un switch, ce qui est bien entendu le contraire ! Vous pouvez par-exemple matcher sur un ensemble de valeurs :
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 | let foo: i32 = 117; match foo { 100...120 => println!("foo est entre 100 et 120 !"), x => println!("You know nothing, John. (foo vaut {})", x), // s'efforcera de trouver une réponse } |
Le pattern matching est très puissant, n'hésitez pas à en user et en abuser !
Vous pouvez exécuter l'exemple ici.
Vous pouvez retrouver une source abordant le pattern matching (avec plusieurs exemples).
Partie de la documentation officielle abordant l'implémentation du pattern matching.
Le mot-clé ref est une alternative au caractère spécial & pour expliciter le renvoi d'une référence d'un objet :
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 | struct A ; fn main() { let foo : A = A ; let bar : &A = &foo ; // ou let ref bar = foo ; } |
Il permet aussi de dire explicitement qu'une valeur ne doit pas être "bougée"/move dans certains contextes.
Le mot-clé mut permet de rendre l'une de vos variables mutable lors de sa déclaration.
Code Rust : | Sélectionner tout |
1 2 3 4 | let mut foo : i32 = 0 ; let bar : i32 = 1 ; foo = 1 ; bar = 2 ; // erreur |
Il se peut que vous ayez omis la particularité de Rust : tout est immuable par défaut.
Pour permettre à une variable de modifier son contenu, il vous faudra utiliser le mot-clé mut.
À quoi sert le mot-clé mut ?
Une macro est ce que l'on peut appeler vulgairement une fonction très puissante.
Grâce aux macros, nous pouvons capturer plusieurs groupes d'expressions et ainsi écrire les instructions désirées selon chaque cas.
Pour grossir un peu le trait : les macros sont une extension du compilateur de Rust. Elles sont interprétées au moment de la compilation, pas pendant l'exécution de votre programme.
Comment utiliser une macro ?
Pour utiliser une macro, il faut d'abord la déclarer en utilisant le mot-clé macro_rules!. (Attention, il ne doit pas y avoir d'espace(s) entre macro_rules et le point d'exclamation.)
Code Rust : | Sélectionner tout |
1 2 3 4 | macro_rules! foo { () => (); } |
Toutes les macros (y compris celle présentée ici) respectent une règle très importante : elles doivent toutes capturer au moins une expression pour être valides et compilées (en l'occurrence, la regex () => ();).
C'est donc cela, l'une des différences majeures entre une fonction/procédure et une macro : cette dernière est capable de capturer des expressions rationnelles, conserver en mémoire ce que désire le développeur, puis de les réutiliser dans le corps de l'une d'entre-elles.
Ces « super » fonctions demandent donc quelques notions liées aux expressions rationnelles pour vous permettre d'apprécier pleinement ce puissant mécanisme.
Voici un exemple très basique de macro :
Vous aurez certainement remarqué que les paramètres passés sont assez spéciaux ; au lieu d'avoir le nom de leur type après les deux points (« : »), il est écrit expr.
C'est ce que l'on appelle un « spécificateur » .
Les spécificateurs sont des types d'entrées supportés par les macros; Il faut noter toutefois que ces fameux types font abstraction de la nature de l'entrée. (e.g. que l'entrée soit une châine de caractères ou un entier importe peu)
Vous pouvez retrouver, ci-dessous, une traduction de chaque description des spécificateurs proposés par le langage:
- ident: un identifiant. e.g. x; foo.
- path: une représentation d'un chemin "package-style"(e.g. T::SpecialA, std::path::Path).
- expr: une expression (e.g. 2 + 2; if true { 1 } else { 2 }; f(42)).
- ty: Un type (e.g. i32, &T, Vec<(char, String)>).
- pat: Un modèle/pattern (e.g. Some(t); (17, 'a'); _).
- stmt: Une (et une seule) déclaraction (e.g. let some = 3).
- block: Une suite de déclarations enveloppée par des crochets et/ou une expression (e.g. {log(error, "hi"); return 12;}).
- item: Un objet (e.g. fn foo(){}; struct Bar;).
- meta: Un "meta objet", plus connu sous le nom d'attributs (e.g. cfg(target_os = "windows")).
- tt: Un token tree. Certainement le type le plus puissant de la liste, puisqu'il vous permet de soumettre tout ce qui existe dans la syntaxe du langage.
Le mot-clé usize permet de laisser le compilateur choisir la taille en mémoire d'un entier non-signé (selon l'architecture de la machine sur laquelle le programme sera exécuté).
Le mot-clé isize permet de laisser le compilateur choisir la taille en mémoire d'un entier signé (selon l'architecture de la machine sur laquelle le programme sera exécuté).
Rust dispose d'un outil de développement multifonction nommé Cargo.
Cargo est en premier lieu un gestionnaire de paquets (qui vous permet donc de télécharger des modules Rust développés par d'autres programmeurs), mais vous épaule également dans la gestion, la construction de vos projets, la création de vos manifests, etc.
Un groupe de Q/R a été créé sur cette FAQ présentant une liste non-exhaustive de commandes supportées par Cargo suivi d'un exemple d'utilisation (vous pourrez également retrouver des exemples dans le manuel officiel de l'outil ($ man cargo) :
- Comment créer un projet avec Cargo ?
- Quel type de projet puis-je créer avec Cargo ?
- Comment compiler son projet ?
- Peut-on générer de la documentation avec Cargo ?
- Où trouver de nouvelles bibliothèques ?
- Comment installer de nouvelles bibliothèques ?
- Comment publier sa bibliothèque faite-maison ?
- Comment lancer des tests avec Cargo ?
- Comment mettre à jour mes bibliothèques ?
Pour utiliser vos fonctions en dehors de votre module, il vous faudra utiliser le mot-clé pub.
Pour comparer deux objets avec Rust, vous pouvez implémenter le trait PartialEq que vous pourrez ensuite utiliser avec == ou la méthode eq.
Exemple :
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | fn main() { let foo = 0 ; let bar = 0 ; let baz = foo.eq(&bar) ; //true let bazz = "Hello world !"; let bazzz = "Hello world !".to_string() ; let bazzzz = bazz.eq(&bazzz) ; //true } |
Le shadowing consiste à faire abstraction des identificateurs qui pourraient être identiques à ceux se trouvant dans un scope (« champ ») plus petit, ou étranger à celui des autres identificateurs dans l'absolu.
Exemple :
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | fn main() -> () { let foo : &str = "Hello"; { let foo : &str = "world!"; println!("{}", &foo); } println!("{}", &foo); } |
La première déclaration de foo a été « éclipsée » par celle se trouvant dans le deuxième scope. Lorsque cette dernière a été détruite (ou simplement mise hors d'accès, dans ce cas), la première déclaration de foo a été de nouveau opérationnelle.
Résultat :
Code : | Sélectionner tout |
1 2 | world! Hello |
Avec Rust, il est possible d'effectuer une « déstructuration » sur certains types de données, mais qu'est-ce que cela signifie exactement ?
Grâce au pattern matching, il est possible de créer, donc, des « modèles » pour isoler une partie de la structure et ainsi vérifier si notre entrée correspond à nos attentes.
Une déstructuration peut se faire sur :
- les listes;
- les tuples;
- les énumérations;
- les structures.
Pour isoler une valeur contenue dans un tuple, il faut d'abord écrire son modèle pour savoir où le chercher.
Par exemple, en assumant que nous cherchons une suite de chiffres dans un ordre croissant, il est simple de déterminer si cette suite est dans le bon ordre ou non.
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | let foo = ("one", "two", "three"); let bar = ("two", "one", "three"); match bar { ("one", x, "three") => { if x == "two" { println!("tout est en ordre !"); } }, _ => println!("on dirait qu'il y a un problème dans votre tuple..."), } |
Lorsque vous construisez un modèle de ce type, gardez bien en tête que la valeur la plus à gauche représentera toujours la première valeur du tuple, et la plus à droite représentera toujours la dernière valeur du tuple.
Rien ne vous empêche donc de faire ceci :
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | let foo = ("one", "two", "three"); let bar = ("two", "one", "three"); match foo { ("one", x, y) => { if (x, y) == ("two", "three") //on surveille plusieurs valeurs { println!("tout est en ordre !"); } }, _ => println!("on dirait qu'il y a un problème dans votre tuple..."), } |
Le pattern matching vous donne la possibilité de « décortiquer » une énumération, vous permettant ainsi d'effectuer des tests complets.
Voici un exemple :
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | pub enum Enum { One, Two, Three, Four, } fn foo(arg: Enum) -> () { match arg { Enum::One => { println!("One"); }, Enum::Two => { println!("Two"); }, Enum::Three => { println!("Three"); }, Enum::Four => { println!("Four"); }, } } fn main() { let (bar, baz, bazz, bazzz) = (Enum::One, Enum::Two, Enum::Three, Enum::Four); foo(bar); foo(baz); foo(bazz); foo(bazzz); } |
Tout d'abord, la question que nous pourrions nous poser est : en quoi consiste la destructuration sur une structure ?
L'idée est d'isoler, encore une fois, les propriétés qui nous intéressent.
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | struct A { x: String, y: String, z: String, } fn main() -> () { let foo = A { x: "Hello".to_string(), y: " ".to_string(), z: "world!".to_string(), }; let A {x: a, y: b, z: c} = foo; //on décortique les attributs de notre structure println!("{}{}{}", a, b, c); //puis on les utilise dans de nouvelles variables } |
Vous souhaiteriez omettre un attribut ? Pas de problèmes !
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 | let foo = A { x: "Hello".to_string(), y: " ".to_string(), z: "world!".to_string(), }; let A {x: a, y: b, ..} = foo; //on décortique les attributs de notre structure println!("{}{}", a, b); //puis on les utilise dans de nouvelles variables |
Vous pouvez également isoler ce style d'opération dans un scope plus petit (empêchant l'utilisation des variables temporaires en dehors de ce dernier) comme ceci :
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 | let foo = A { x: "Hello".to_string(), y: " ".to_string(), z: "world!".to_string(), }; { let A {x: a, y: b, z: c} = foo; //on décortique les attributs de notre structure println!("{}{}{}", a, b, c); //puis on les utilise dans de nouvelles variables } //a,b et c ne pourront plus être utilisés à partir d'ici |
La bibliothèque standard de Rust propose un(e) trait/ interface nommé(e) PartialEq composée de deux fonctions :
- fn eq(&self, other: &instance_de_la_meme_structure);;
- fn ne(&self, other: &instance_de_la_meme_structure);.
Comme il est stipulé dans la documentation officielle, vous n'êtes pas forcé d'implémenter les deux fonctions : ne() étant simplement le contraire de eq() et vice versa, il serait redondant de les implémenter dans la même structure.
Il se pourrait que vous ayez omis d'utiliser une annotation : #[macro_use].
Cette dernière permet d'exporter toutes les macros qui doivent être publiques pour être utilisées à l'extérieur de la bibliothèque.
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 | #[macro_use] extern crate votre_lib; fn main() -> () { votre_macro!(); } |
Si vous ne parvenez toujours pas à les utiliser, il est possible que vous ayez omis l'annotation #[macro_export] dans les modules comportant vos macros.
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // dans le fichier lib.rs #[macro_use]//bien préciser que ce module utilise des macros pub mod votre_conteneur { #[macro_export] macro_rules! foo { () => (); } #[macro_export] macro_rules! bar { () => (); } #[macro_export] macro_rules! baz { () => (); } } |
Si votre problème persiste, je vous invite à vous rendre sur les forums figurant dans la rubrique programmation pour obtenir de l'aide. Présentez clairement l'erreur que le compilateur vous renvoie dans votre post.
La combinaison des deux mot-clés permet d'assigner, de manière concise, du contenu à une variable.
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | fn main() -> () { let foo : Option<String> = Some("Hello world!".to_string()); let mut bar : bool = false; if let Some(content) = foo // si la variable foo contient quelque chose... { bar = true; } else { println!("foo's content is None"); } } |
C'est un moyen simple et efficace d'assigner du contenu sans passer par le pattern matching.
La combinaison des deux mot-clés permet d'effectuer des tests concis et ainsi nous éviter de passer par le pattern matching lorsque cela n'est pas nécessaire. (while let peuvent s'avérer très utiles lorsqu'il faut tester à chaque itération si le fichier contient toujours quelque chose).
[Exemple de la documentation officielle]
Code Rust : | Sélectionner tout |
1 2 3 4 | let mut v = vec![1, 3, 5, 7, 11]; while let Some(x) = v.pop() { println!("{}", x); } |
Le marqueur 'b' préfixant parfois une chaîne de caractères littérale permet de la convertir en une slice d'octets dont la longueur est égale à la somme de la taille (en octet) de chaque caractère composant la chaîne.
Prenons le caractère espagnol suivant: "ñ" qui est un caractère qui ne fait pas partie de la table ASCII et est codé sur 3 octets:
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 8 | fn main() { let foo: &'static str = "ñ"; // une string composée d'un seul caractère. let bar: &'static [u8; 3] = b"\x00F1"; // la slice contient trois éléments (i.e. les trois octets que consomment "ñ"). let baz: &'static str = "foo"; let bang: &'static [u8; 3] = b"foo"; // contient trois éléments car chacun d'entre-eux // n'est codé que sur un seul octet. } |
Il existe autant de tuples que de possibilités de combinaisons entre les types. Chaque tuple est un type à part entière dont la signature sera différencier d'un autre (à condition que ce dernier ait une signature significativement différente).
Lecture d'un tuple
Comme il peut être le cas pour les tableaux primitifs, les éléments contenus par un tuple sont indexés de 0 à n-1 où n est égal au nombre d'éléments composant le tuple. En revanche, il faut utiliser la notation pointée pour accéder à l'élément voulu, au lieu de l'expression classique [N] des tableaux.
Code Rust : | Sélectionner tout |
1 2 3 4 5 | fn main() { let mut foo: (String,) = ("Hello,".to_owned(),); // On récupère le premier élément. foo.0.push_str(" world!"); } |
Code Rust : | Sélectionner tout |
1 2 3 4 5 | fn main() { let mut foo: (String,) = ("Hello,".to_owned(),); // error[E0608]: cannot index into a value of type `(std::string::String,)` foo[0].push_str(" world!"); } |
Modification des éléments du tuple
A l'instar des autres types de ressources, les éléments d'un tuple ne peuvent être modifiés que si ce dernier est mutable.
Sauf si l'un des éléments est explicitement défini comme mutable (par le biais d'une référence), les éléments hériteront des droits d'accès du tuple.
Code Rust : | Sélectionner tout |
1 2 3 4 5 | fn main() { let foo: (String,) = ("Hello,".to_owned(),); // error[E0596]: cannot borrow field `foo.0` of immutable binding as mutable foo.0.push_str(" world!"); } |
Code Rust : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | fn main() { let mut foo: (String,) = ("Hello,".to_owned(),); foo.0.push_str(" world!"); { let bar: (&mut String,) = (&mut foo.0,); bar.0.push_str("\nHello there!"); } } // Displays: // Hello, world! // Hello there! |
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.