Aller au contenu

Les tuples en Rust

Un tuple regroupe plusieurs valeurs, qui peuvent être de types différents, en une seule. C’est un moyen rapide de rassembler des choses qui vont ensemble sans devoir définir un nouveau type pour elles.

fn main() {
let person: (&str, i32, bool) = ("Alice", 30, true);
println!("{:?}", person);
}

Sortie :

("Alice", 30, true)
▶ Essayez par vous-même

Remarquez que le tuple mélange un &str, un i32 et un bool dans une seule valeur, ce qu’un tableau ne peut pas faire, puisque tous les éléments d’un tableau doivent être du même type.

Pour afficher un tuple entier, utilisez {:?} (le format de débogage) au lieu de {}. Le simple {} ne fonctionne pas avec les tuples.

Utilisez .0, .1, .2, et ainsi de suite : les positions d’un tuple commencent à zéro.

fn main() {
let person = ("Alice", 30, true);
println!("Nom : {}", person.0);
println!("Âge : {}", person.1);
println!("Actif : {}", person.2);
}

Sortie :

Nom : Alice
Âge : 30
Actif : true
▶ Essayez par vous-même

La position doit être un nombre écrit tel quel dans le code. Vous ne pouvez pas utiliser une variable, comme person.i, car chaque position peut contenir un type différent, et Rust doit connaître le type au moment de la compilation.

Vous pouvez extraire chaque valeur dans sa propre variable en une seule ligne :

fn main() {
let person = ("Alice", 30, true);
let (name, age, is_active) = person;
println!("{name} a {age} ans, active : {is_active}");
}

Sortie :

Alice a 30 ans, active : true
▶ Essayez par vous-même

C’est souvent plus clair que de répéter .0, .1, .2, surtout quand un tuple a plus de deux ou trois champs.

Les tuples sont la façon naturelle de renvoyer plus d’une valeur, puisqu’une fonction ne peut déclarer qu’un seul type de retour :

fn min_max(numbers: &[i32]) -> (i32, i32) {
let min = *numbers.iter().min().unwrap();
let max = *numbers.iter().max().unwrap();
(min, max)
}
fn main() {
let (smallest, largest) = min_max(&[4, 8, 1, 9, 3]);
println!("min : {smallest}, max : {largest}");
}

Sortie :

min : 1, max : 9
▶ Essayez par vous-même

Ne vous préoccupez pas encore de &[i32], .iter() ou .unwrap(). Ils sont expliqués aux pages Slices, Itérateurs et Option. Ce qu’il faut remarquer ici, c’est le type de retour (i32, i32) et la façon dont main le déstructure tout de suite en deux variables.

Si un tuple est déclaré avec mut, vous pouvez modifier ses champs un par un, avec les mêmes positions .0, .1. Les valeurs peuvent changer, mais pas la forme : un tuple garde toujours le nombre et les types de champs qu’il avait au départ.

La déstructuration fonctionne aussi dans l’autre sens : vous pouvez affecter des valeurs à des variables existantes. Cela donne un échange élégant en une ligne, sans variable temporaire. Si vous ne voulez que certains champs, écrivez _ pour ceux dont vous n’avez pas besoin :

fn main() {
let mut point = (3, 4);
point.0 += 10; // on modifie un champ sur place
println!("le point vaut {:?}", point);
let mut a = 1;
let mut b = 2;
(a, b) = (b, a); // on échange les deux valeurs
println!("a = {a}, b = {b}");
let (x, _) = point; // on garde x, on ignore le deuxième champ
println!("x = {x}");
}

Sortie :

le point vaut (13, 4)
a = 2, b = 1
x = 13
▶ Essayez par vous-même

L’échange fonctionne parce que Rust construit d’abord le côté droit, (b, a). Ce n’est qu’ensuite qu’il le décompose dans a et b. Vous pouvez faire bien plus avec _ et les patterns (motifs) : voyez la page Pattern matching.

En fait, vous avez déjà rencontré un tuple sans le savoir. () s’appelle le type unité (unit type), et c’est ce que renvoie une fonction qui ne renvoie rien d’utile : c’est le type de retour implicite de fn main().

Les tuples sont parfaits pour des regroupements rapides et sans nom, en particulier pour renvoyer deux ou trois valeurs depuis une fonction. Dès qu’un groupe de valeurs représente un vrai concept avec des champs nommés (comme une Person avec un nom et un âge auxquels vous ferez référence par leur nom partout dans votre code), une struct est généralement un choix meilleur et plus lisible.

Écrivez une fonction sum_and_product(a: i32, b: i32) -> (i32, i32) qui renvoie à la fois la somme et le produit de deux nombres. Dans main, appelez-la avec 5 et 6 et déstructurez le résultat. Indice : la dernière ligne de la fonction est le tuple lui-même, (a + b, a * b), sans point-virgule.

Voir la solution
fn sum_and_product(a: i32, b: i32) -> (i32, i32) {
(a + b, a * b)
}
fn main() {
let (sum, product) = sum_and_product(5, 6);
println!("somme = {sum}, produit = {product}");
}

Sortie :

somme = 11, produit = 30
▶ Essayez par vous-même

On peut stocker un rectangle sous forme de tuple de deux points, chaque point étant lui-même un tuple : let rect = ((1, 2), (5, 5));. Calculez sa largeur, sa hauteur et son aire. Indice : les tuples peuvent être imbriqués, et rect.1.0 signifie « le premier champ du deuxième point ».

Voir la solution
fn main() {
let rect = ((1, 2), (5, 5));
let width = rect.1.0 - rect.0.0;
let height = rect.1.1 - rect.0.1;
println!("largeur {width}, hauteur {height}, aire {}", width * height);
}

Sortie :

largeur 4, hauteur 3, aire 12
▶ Essayez par vous-même

Vous pouvez aussi déstructurer les deux points d’un coup : let ((x1, y1), (x2, y2)) = rect;. C’est plus long à écrire, mais x2 - x1 se lit plus facilement que rect.1.0 - rect.0.0. Quand les points commencent à s’accumuler comme ça, c’est le signe qu’une struct avec des champs nommés serait plus claire.

  • Un tuple regroupe des valeurs qui peuvent être de types différents : (name, age, is_active).
  • On accède aux champs avec .0, .1, .2, ou on les extrait tous d’un coup par déstructuration.
  • Les tuples sont une façon naturelle de renvoyer plusieurs valeurs depuis une fonction.
  • Les champs d’un tuple mut peuvent changer, mais pas sa taille ni ses types. (a, b) = (b, a); échange deux valeurs.
  • Pour des regroupements nommés et réutilisables, préférez une struct.

Petit quiz

1. Que peut contenir un tuple qu'un tableau ne peut pas contenir ?

2. Comment accéder au deuxième élément d'un tuple appelé person ?

3. Après let mut a = 1; let mut b = 2; (a, b) = (b, a);, que valent a et b ?

Score : 0 / 3