Phase 3Structures de données

#11 Option et Result

Option<T>, Result<T,E>, ?

Le type Option

En Rust, il n'y a pas de valeur null. À la place, on utilise le type Option<T> pour représenter une valeur qui peut être présente ou absente. C'est un enum avec deux variantes :

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

Option est si courant qu'il est importé automatiquement : vous pouvez utiliser Some et None directement.

Exemple basique
fn trouver_utilisateur(id: u32) -> Option<String> {
    if id == 1 {
        Some(String::from("Alice"))
    } else {
        None
    }
}

fn main() {
    let user = trouver_utilisateur(1);
    println!("{:?}", user); // Some("Alice")

    let inconnu = trouver_utilisateur(42);
    println!("{:?}", inconnu); // None
}

Travailler avec Option

Plusieurs méthodes permettent d'extraire ou de transformer la valeur contenue dans un Option.

unwrap et expect

unwrap() extrait la valeur si elle existe, mais panique si c'est None. Préférez expect() qui affiche un message d'erreur personnalisé.

unwrap et expect
let valeur: Option<i32> = Some(42);
let x = valeur.unwrap();       // 42
let y = valeur.expect("oups"); // 42

let vide: Option<i32> = None;
// vide.unwrap(); // PANIC !

map et and_then

map applique une fonction à la valeur interne sans la déballer manuellement. and_then (aussi appelé flatmap) est utile quand la fonction retourne elle-même un Option.

map et and_then
let nom: Option<String> = Some(String::from("Alice"));

// map : transformer la valeur interne
let longueur: Option<usize> = nom.map(|n| n.len());
println!("{:?}", longueur); // Some(5)

// and_then : chaîner des Option
fn premiere_lettre(s: String) -> Option<char> {
    s.chars().next()
}

let lettre = Some(String::from("Rust")).and_then(premiere_lettre);
println!("{:?}", lettre); // Some('R')

Le type Result

Result<T, E> est utilisé quand une opération peut échouer. Contrairement à Option, il fournit des informations sur pourquoi l'opération a échoué.

Définition de Result
enum Result<T, E> {
    Ok(T),   // succès avec une valeur
    Err(E),  // erreur avec des détails
}
Exemple avec Result
use std::fs;

fn lire_fichier(chemin: &str) -> Result<String, std::io::Error> {
    fs::read_to_string(chemin)
}

fn main() {
    match lire_fichier("hello.txt") {
        Ok(contenu) => println!("Le fichier contient : {}", contenu),
        Err(e) => println!("Erreur : {}", e),
    }
}

L'opérateur ?

L'opérateur ? est un raccourci élégant : il extrait la valeur si c'est Ok (ou Some), et retourne immédiatement l'erreur si c'est Err (ou None).

L'opérateur ?
use std::fs;
use std::io;

fn lire_et_compter(chemin: &str) -> Result<usize, io::Error> {
    let contenu = fs::read_to_string(chemin)?; // retourne Err si échec
    Ok(contenu.len())
}

fn main() {
    match lire_et_compter("hello.txt") {
        Ok(n) => println!("Le fichier fait {} octets", n),
        Err(e) => println!("Erreur : {}", e),
    }
}

Convertir entre Option et Result

Vous pouvez convertir un Option en Result avecok_or, et un Result en Option avecok().

Conversions
// Option -> Result
let opt: Option<i32> = Some(42);
let res: Result<i32, &str> = opt.ok_or("valeur absente");
println!("{:?}", res); // Ok(42)

let vide: Option<i32> = None;
let err: Result<i32, &str> = vide.ok_or("valeur absente");
println!("{:?}", err); // Err("valeur absente")

// Result -> Option
let res: Result<i32, &str> = Ok(10);
let opt: Option<i32> = res.ok();
println!("{:?}", opt); // Some(10)

À vous de jouer

Essayez les commandes ci-dessous pour compiler et exécuter le programme :

terminal — cargo
user@stemlegacy:~/option-result-demo$