Les structs en Rust
Une struct (structure) regroupe des valeurs liées entre elles dans un seul type nommé. Là où un tuple est un petit paquet rapide et anonyme, une struct donne un nom à chaque champ, ce qui rend le code qui l’utilise bien plus facile à lire.
Définir et créer une struct
Section intitulée « Définir et créer une struct »struct Person { name: String, age: u32,}
fn main() { let alice = Person { name: String::from("Alice"), age: 30, };
println!("{} a {} ans", alice.name, alice.age);}Sortie :
Alice a 30 ansComparez avec un tuple ("Alice", 30) : avec une struct, alice.name et alice.age se comprennent tout seuls. Vous n’avez pas besoin de vous rappeler que le champ 0 est le nom et le champ 1 l’âge.
Rendre une struct mutable
Section intitulée « Rendre une struct mutable »Comme les variables, les instances de struct sont immuables par défaut. Ajoutez mut pour pouvoir modifier un champ après la création :
struct Person { name: String, age: u32,}
fn main() { let mut alice = Person { name: String::from("Alice"), age: 30, };
alice.age += 1; println!("{} a maintenant {} ans", alice.name, alice.age);}Ajouter du comportement avec des méthodes
Section intitulée « Ajouter du comportement avec des méthodes »Vous pouvez attacher des fonctions à une struct avec un bloc impl. À l’intérieur, &self désigne l’instance précise sur laquelle la méthode a été appelée :
struct Person { name: String, age: u32,}
impl Person { fn greet(&self) { println!("Bonjour, je m'appelle {} et j'ai {} ans", self.name, self.age); }}
fn main() { let alice = Person { name: String::from("Alice"), age: 30 }; alice.greet();}Sortie :
Bonjour, je m'appelle Alice et j'ai 30 ansLes fonctions de type constructeur
Section intitulée « Les fonctions de type constructeur »Un motif courant consiste à écrire une fonction new dans le bloc impl qui construit une instance pour vous. Ainsi, ceux qui l’appellent n’ont pas à écrire chaque champ à la main :
struct Person { name: String, age: u32,}
impl Person { fn new(name: &str, age: u32) -> Person { Person { name: name.to_string(), age } }}
fn main() { let bob = Person::new("Bob", 25); println!("{} a {} ans", bob.name, bob.age);}Remarquez age tout seul dans Person { name: name.to_string(), age } : quand une variable porte exactement le même nom que le champ, vous pouvez éviter d’écrire age: age et écrire simplement age. Rust appelle cela le field init shorthand (raccourci d’initialisation des champs).
Afficher une struct avec derive
Section intitulée « Afficher une struct avec derive »Essayez println!("{:?}", alice) sur la struct telle qu’elle est écrite plus haut : ça ne compile pas. Rust ne sait pas afficher un type personnalisé en mode debug tant que vous ne le lui demandez pas. Ajoutez #[derive(Debug)] au-dessus de la struct pour l’obtenir gratuitement :
#[derive(Debug)]struct Person { name: String, age: u32,}
fn main() { let alice = Person { name: String::from("Alice"), age: 30 }; println!("{:?}", alice);}Sortie :
Person { name: "Alice", age: 30 }#[derive(...)] est la façon dont Rust génère automatiquement des fonctionnalités courantes pour un type. Vous verrez sans arrêt Debug, et plus tard Clone et d’autres, ajoutés de cette manière.
Exercices
Section intitulée « Exercices »1. Les méthodes d’un rectangle
Section intitulée « 1. Les méthodes d’un rectangle »Créez une struct Rectangle avec des champs width (largeur) et height (hauteur) de type u32. Donnez-lui deux méthodes : area, qui renvoie l’aire, et is_square, qui renvoie true quand les deux côtés sont égaux. Indice : les deux méthodes ne font que lire les champs, elles prennent donc &self.
Voir la solution
struct Rectangle { width: u32, height: u32,}
impl Rectangle { fn area(&self) -> u32 { self.width * self.height }
fn is_square(&self) -> bool { self.width == self.height }}
fn main() { let wide = Rectangle { width: 8, height: 3 }; let square = Rectangle { width: 5, height: 5 };
println!("large : aire {}, carré ? {}", wide.area(), wide.is_square()); println!("carré : aire {}, carré ? {}", square.area(), square.is_square());}Sortie :
large : aire 24, carré ? falsecarré : aire 25, carré ? true2. Un compte bancaire
Section intitulée « 2. Un compte bancaire »Créez une struct Account avec un owner (titulaire, une String) et un balance (solde) en centimes (u32). Écrivez Account::new("Ana"), qui démarre avec un solde de 0. Déposez ensuite 50 puis 25 centimes en modifiant le champ, et affichez le compte de deux façons : avec une phrase normale et avec {:?}. Indice : la variable doit être déclarée avec let mut pour que vous puissiez modifier account.balance, et {:?} a besoin de #[derive(Debug)].
Voir la solution
#[derive(Debug)]struct Account { owner: String, balance: u32,}
impl Account { fn new(owner: &str) -> Account { Account { owner: owner.to_string(), balance: 0 } }}
fn main() { let mut account = Account::new("Ana"); account.balance += 50; account.balance += 25;
println!("{} a {} centimes", account.owner, account.balance); println!("{:?}", account);}Sortie :
Ana a 75 centimesAccount { owner: "Ana", balance: 75 }new fixe lui-même balance: 0, donc personne ne peut l’oublier en l’appelant. C’est l’une des grandes raisons pour lesquelles les fonctions constructeurs sont si courantes.
3. Une liste de personnes
Section intitulée « 3. Une liste de personnes »Mettez trois valeurs Person dans un vecteur : Ana (34 ans), Ben (16 ans) et Chen (52 ans). Affichez le nom de toutes les personnes de 18 ans ou plus, puis affichez qui est la plus âgée. Indice : parcourez la liste avec for person in &people, et retenez la personne la plus âgée trouvée jusqu’ici avec let mut oldest = &people[0];.
Voir la solution
struct Person { name: String, age: u32,}
fn main() { let people = vec![ Person { name: String::from("Ana"), age: 34 }, Person { name: String::from("Ben"), age: 16 }, Person { name: String::from("Chen"), age: 52 }, ];
let mut oldest = &people[0]; for person in &people { if person.age >= 18 { println!("{} est adulte", person.name); } if person.age > oldest.age { oldest = person; } }
println!("La personne la plus âgée : {} ({} ans)", oldest.name, oldest.age);}Sortie :
Ana est adulteChen est adulteLa personne la plus âgée : Chen (52 ans)oldest est une référence (&Person) vers un élément du vecteur, pas une copie : rien n’est cloné. Chaque fois que nous trouvons quelqu’un de plus âgé, nous faisons simplement pointer oldest vers cette personne.
Récapitulatif
Section intitulée « Récapitulatif »- Une struct regroupe des champs nommés dans un seul type :
struct Person { name: String, age: u32 }. - Créez une instance avec
Person { name: ..., age: ... }; déclarez-lamutpour pouvoir modifier ses champs ensuite. - Ajoutez du comportement avec un bloc
impl;&selfdésigne l’instance sur laquelle la méthode a été appelée. - Ajoutez
#[derive(Debug)]pour pouvoir afficher une struct avec{:?}.
Petit quiz
Section intitulée « Petit quiz »Petit quiz
1. Quel est le principal avantage d'une struct par rapport à un tuple ?
2. Qu'est-ce que l'ajout de #[derive(Debug)] à une struct vous permet de faire ?
3. Dans Person { name: name.to_string(), age }, que fait le age tout seul ?
Score : 0 / 3
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Defining and Instantiating Structs — The Rust Book (en anglais) : présente aussi la syntaxe de mise à jour (
..other), les tuple structs et les structs unitaires. - An Example Program Using Structs — The Rust Book (en anglais) : transforme un programme de rectangle, des variables séparées à un tuple puis à une struct, pour que vous voyiez ce que les structs apportent.
- Method Syntax — The Rust Book (en anglais) :
&selfou&mut self, les méthodes avec des paramètres en plus, et les fonctions associées commenew. - Structures — Rust by Example (en anglais) : des exemples exécutables de chaque sorte de struct, avec un petit exercice à la fin.
- Derive — Rust by Example (en anglais) : ce que
#[derive(...)]peut générer pour vous, commeDebug,CloneetPartialEq.