Rust

Any donation is very welcome
Fork me on GitHub

I. Les bases de la programmation en Rust

10. Gestion des erreurs

Il est courant dans d'autres langages de voir ce genre de code :

Objet *obj = creer_objet();

if (obj == NULL) {
    // gestion de l'erreur
}

Vous ne verrez (normalement) pas ça en Rust.

Result

Créons un fichier par exemple :

Runuse std::fs::File;

let mut fichier = File::open("fichier.txt");

La documentation dit que File::open renvoie un Result. Il ne nous est donc pas possible d'utiliser directement la variable fichier. Cela nous "oblige" à vérifier le retour de File::open :

Runuse std::fs::File;

let mut fichier = match File::open("fichier.txt") {
    Ok(f) => {
        // Okay, l'ouverture du fichier s'est bien déroulée, on renvoie l'objet
        f
    },
    Err(e) => {
        // Il y a eu un problème, affichons l'erreur pour voir ce qu'il se passe
        println!("{}", e);
        // on ne peut pas renvoyer le fichier ici, donc on quitte la fonction
        return;
    }
};

Il est cependant possible de passer outre cette vérification, mais c'est à vos risques et périls !

Runuse std::fs::File;

let mut fichier = File::open("fichier.txt").expect("erreur lors de l'ouverture");

Si jamais il y a une erreur lors de l'ouverture du fichier, votre programme plantera et vous ne pourrez rien y faire. Il est toutefois possible d'utiliser cette méthode de manière "sûre" avec les fonctions is_ok et is_err :

Runuse std::fs::File;

let mut fichier = File::open("fichier.txt");

if fichier.is_ok() {
    // on peut faire unwrap !
} else {
    // il y a eu une erreur, unwrap impossible !
}

Utiliser le pattern matching est cependant préférable.

À noter qu'il existe un équivalent de la méthode expect qui s'appelle unwrap. Elle fait exactement la même chose mais ne permet pas de fournir un message d'erreur. Pour faire simple, NE L'UTILISEZ JAMAIS !!

Option

Vous savez maintenant qu'il n'est normalement pas possible d'avoir des objets invalides. Exemple :

Runlet mut v = vec!(1, 2);

v.pop(); // retourne Some(2)
v.pop(); // retourne Some(1)
v.pop(); // retourne None

Cependant, il est tout à fait possible que vous ayez besoin d'avoir un objet qui serait initialisé plus tard pendant le programme ou qui vous permettrait de vérifier un état. Dans ce cas comment faire ? Option est là pour ça !

Imaginons que vous ayez un vaisseau costumisable sur lequel il est possible d'avoir des bonus (disons un salon intérieur). Il ne sera pas là au départ, mais peut être ajouté par la suite :

Runstruct Vaisseau {
    // pleins de trucs
    salon: Option<Salon>,
}

impl Vaisseau {
    pub fn new() -> Vaisseau {
        Vaisseau {
            // on initialise le reste
            salon: None, // on n'a pas de salon
        }
    }
}

let mut vaisseau = Vaisseau::new();

Donc pour le moment, on n'a pas de salon. Maintenant nous en rajoutons un :

Runvaisseau.salon = Some(Salon::new());

Je présume que vous vous demandez comment accéder au salon maintenant. Tout simplement comme ceci :

Runmatch vaisseau.salon {
    Some(s) => {
        println!("ce vaisseau a un salon");
    },
    None => {
        println!("ce vaisseau n'a pas de salon");
    }
}

Au début, vous risquez de trouver ça agaçant, mais la sécurité que cela apporte est un atout non négligeable ! Cependant, tout comme avec Result, vous pouvez utiliser la méthode expect.

Runvaisseau.salon = Some(Salon::new());

let salon =  vaisseau.salon.expect("pas de salon"); // je ne le recommande pas !

Tout comme avec Result, il est possible de se passer du mécanisme de pattern matching avec les méthodes is_some et is_none :

Runif vaisseau.salon.is_some() {
    // on peut unwrap !
} else {
    // ce vaisseau ne contient pas de salon !
}

Encore une fois, utiliser le pattern matching est préférable.

panic!

panic! est une macro très utile puisqu'elle permet de "quitter" le programme. Elle n'est à appeler que lorsque le programme a une erreur irrécupérable. Elle est très simple d'utilisation :

Runpanic!();
panic!(4); // panic avec une valeur de 4 pour la récupérer ailleurs (hors du programme par exemple)
panic!("Une erreur critique vient d'arriver !");
panic!("Une erreur critique vient d'arriver : {}", "le moteur droit est mort");

Et c'est tout.

try!

Et maintenant voici la macro try! ! Elle permet de se "passer" du pattern matching en retournant directement le résultat en cas de réussite ou bien en quittant la fonction en cas d'erreur. Exemple :

Runlet mut fichier = try!(File::create("fichier.txt"));
try!(fichier.write_all("Test"));

La fonction qui utilise cette macro doit obligatoirent retourner Result.

À noter qu'il y a maintenant la possibilité d'utiliser ?. Cet opérateur fonctionne exactement de la même façon que try! si ce n'est que c'est plus court à écrire :

RunFile::create("fichier.txt")?.write_all("Test")?;

Voilà pour ce chapitre, vous devriez maintenant être capables de créer des codes un minimum sécurisés.