Aller au contenu

Option en Rust

Rust n’a pas de null. Si une valeur peut être absente, cela s’exprime directement dans le type, grâce à Option. Au début, cela ressemble à une formalité de plus, mais cela élimine toute une catégorie de bugs : la fameuse « référence nulle », responsable d’innombrables plantages dans d’autres langages.

Option est une enum à deux variantes :

enum Option<T> {
Some(T), // une valeur est présente
None, // pas de valeur
}

En fait, vous l’avez déjà croisée : .get() sur un vecteur et .get() sur une HashMap renvoient tous les deux une Option.

fn main() {
let has_value: Option<i32> = Some(5);
let no_value: Option<i32> = None;
println!("{:?}", has_value);
println!("{:?}", no_value);
}

Sortie :

Some(5)
None
▶ Essayez par vous-même

La façon sûre de récupérer la valeur, c’est match. Rust vous oblige à traiter les deux cas, si bien que vous ne pouvez pas oublier par accident le cas « absent » :

fn main() {
let age: Option<i32> = Some(30);
match age {
Some(value) => println!("L'âge est {value}"),
None => println!("Aucun âge indiqué"),
}
}
▶ Essayez par vous-même
fn find_user(id: u32) -> Option<&'static str> {
if id == 1 {
Some("Alice")
} else {
None
}
}
fn main() {
match find_user(1) {
Some(name) => println!("Trouvé : {name}"),
None => println!("Utilisateur introuvable"),
}
match find_user(99) {
Some(name) => println!("Trouvé : {name}"),
None => println!("Utilisateur introuvable"),
}
}

Sortie :

Trouvé : Alice
Utilisateur introuvable
▶ Essayez par vous-même

Au lieu de renvoyer null ou de lever une exception quand l’utilisateur n’existe pas, c’est le type de retour de la fonction lui-même qui prévient chaque appelant : « il se peut que je ne trouve rien ». Et le compilateur ne le laissera pas ignorer cette possibilité.

Dans les cas simples, un match complet peut sembler excessif. Voici deux raccourcis :

fn main() {
let age: Option<i32> = None;
let value = age.unwrap_or(0); // valeur de repli : 0 s'il n'y a pas de valeur
println!("{value}");
}
▶ Essayez par vous-même

Il existe aussi .unwrap(), qui vous donne directement la valeur, mais qui panique (fait planter le programme) si c’est None :

let age: Option<i32> = Some(30);
let value = age.unwrap(); // aucun problème ici, puisque age vaut Some

Écrivez fn first_even(numbers: &[i32]) -> Option<i32>, qui renvoie le premier nombre pair de la slice, ou None s’il n’y en a aucun. Affichez le résultat pour [3, 7, 8, 10] avec match, et pour [1, 3, 5] avec .unwrap_or(0). Indice : parcourez la slice et faites return Some(n) dès que n % 2 == 0 ; après la boucle, renvoyez None.

Voir la solution
fn first_even(numbers: &[i32]) -> Option<i32> {
for &n in numbers {
if n % 2 == 0 {
return Some(n);
}
}
None
}
fn main() {
match first_even(&[3, 7, 8, 10]) {
Some(n) => println!("Premier nombre pair : {n}"),
None => println!("Aucun nombre pair"),
}
let fallback = first_even(&[1, 3, 5]).unwrap_or(0);
println!("Avec valeur de repli : {fallback}");
}

Sortie :

Premier nombre pair : 8
Avec valeur de repli : 0
▶ Essayez par vous-même

Créez une HashMap de réglages qui contient seulement "volume", réglé à 80. Ensuite, parcourez les clés "volume" et "luminosité", et affichez la valeur si elle existe, ou un message « non défini » sinon. Indice : .get(key) sur une HashMap renvoie une Option : vous pouvez donc faire un match directement dessus.

Voir la solution
use std::collections::HashMap;
fn main() {
let mut settings = HashMap::new();
settings.insert("volume", 80);
for key in ["volume", "luminosité"] {
match settings.get(key) {
Some(value) => println!("{key} = {value}"),
None => println!("{key} : non défini"),
}
}
}

Sortie :

volume = 80
luminosité : non défini
▶ Essayez par vous-même
  • Option<T> représente une valeur qui peut être présente ou non : Rust n’a pas de null.
  • Some(value) contient quelque chose ; None signifie qu’il n’y a rien.
  • match est la façon sûre de traiter les deux cas ; le compilateur ne vous laissera pas en oublier un.
  • .unwrap_or(default) fournit une valeur de repli ; .unwrap() panique si la valeur est None, alors utilisez-le avec modération.

Petit quiz

1. Qu'utilise Rust à la place de null pour une valeur qui peut être absente ?

2. Que se passe-t-il quand vous appelez .unwrap() sur une valeur None ?

3. Que fait age.unwrap_or(0) quand age vaut None ?

4. Pourquoi match est-il considéré comme la façon sûre de récupérer la valeur d'une Option ?

5. Parmi les méthodes citées sur cette page, lesquelles renvoient déjà une Option ?

Score : 0 / 5