Rust : Error handling

De Justine's wiki
Aller à la navigation Aller à la recherche

<syntaxhighlight lang='rust'> fn main() {

   //Panic ! (Erreurs irrécupérables)
   //On peut causer une panique en faisant une action qui est une erreur,
   //ou avec la macro panic!().
   
   //abort ou unwind
   //Lors d'une panique, par défaut, Rust va remonter la stack pour 
   //libérer la mémoire (unwind).
   //On peut modifier ça en choisissant d'abort (ne rien faire) à la place
   //; c'est utile pour diminuer la taille du binaire.
   //Pour ça, ajouter la section suivante dans Cargo.toml
   //[profile.release]
   //panic = 'abort'
   //Panique
   panic!("Crash and burn");
   //On peut agir sur la backtrace d'erreur via la variable d'environnement
   //RUST_BACKTRACE (en la mettant à 1).
   
   //Result (Erreurs récupérables)
   //
   //Pour rappel, l'enum Result a la forme suivante:
   //enum Result<T, E> {
   //  Ok(T),
   //  Err(E)
   //}
   //T et E sont des types génériques. T est la valeur qui sera retournée
   //sur un succès, E le type d'erreur.

}

use std::fs::File;

fn open_file() {

   let greeting_file_result = File::open("hello.txt");
   //Le retour de File::open est un Result<T, E>. T sera ici un handler de  
   //fichier std::fs:File, E sera un std::io::Error. 
   //On rajoute du code de gestion d'erreur:
   let greeting_file  = match greeting_file_result {
       Ok(file) => file,
       Err(error) => panic!("Problem opening the file : {:?}", error),
   };

}

   //On peut vouloir régir différement en fonction des types d'erreurs.

use std::io::ErrorKind;

fn open_file_2() {

   let greeting_file_result = File::open("hello.txt");
   let greeting_file = match greeting_file_result {
       Ok(file) => file,
       Err(error) => match error.kind() {
           ErrorKind::NotFound => match File::create("hello.txt") {
               Ok(fc) => fc,
               Err(e) => panic!("Coulnd not create the file : {:?}", e)
           },
           //Ici, on est sur le bras catch-all : other_error est juste
           //un nom de variable au pif...
           other_error => {
               panic!("Problem opening the file {:?}", other_error)
           }
       }
   };

}

   //match c'est cool, mais en fait pas tellement.
   //On peut utiliser unwrap(), qui signifie : 
   //Si Ok, renvoie la valeur associée
   //Si Err, panic! 

fn blablabla() {

   let greeting_file = File::open("hello.txt").unwrap();

}

   //On a aussi expect. Expect fait un peu la même chose mais permet
   //de choisir le message de panic (Super, ça valait vraiment le coup
   //de faire 2 méthodes ?). C'est très souvent le meilleur choix. 

fn monculsurlacommode() {

   let greeting_file = File::open("hello.txt")
       .expect("hello.txt should be included in this project");

}


   //Propagation d'erreurs.
   //Quand une fonction rencontre une erreur, elle peut renvoyer son erreur
   //au lieu de la traiter elle-même; c'est la propagation.
   //On peut se contenter de renvoyer un result depuis la fonction si on veut:
   //Ainsi on renvoie soit un result(Ok) avec une valeur, soit Result<Err>
   //avec une erreur.
   //
   //Mais on a un shortcut : le ?
   //
   //Exemple SANS le ?

fn read_username_from_file() -> Result<String, io::Error> {

   let username_file_result = File::open("hello.txt");
   let mut username_file = match username_file_result {
       Ok(file) => file,
       Err(e) => return Err(e),
   };
   let mut username = String::new();
   match username_file.read_to_string(&mut username) {
       Ok(_) => Ok(username),
       Err(e) => Err(e),
   }

}

//Exemple avec le ? //Le résultat est le même qu'avec les match de la fonction au-dessus. //Quand l'opérateur ? est utilisé, le type d'erreur reçu est converti //en fonction du type d'erreur donné dans la signature de la fonction. fn tropsuperlagestionderreur() -> Result<String, io::Error> {

   //Ici, le ? signifie:
   //Si OK : Renvoyer la valeur V dans Ok(V) à username_file;
   //Si Err : Renvoyer l'erreur au code appellant la fonction.
   //? ne peut être utilisé que si la fonction renvoie un result 
   //ou un Option<T>
   let mut username_file = File::open("hello.txt")?;
   let mut username = String::new();
   username_file.read_to_string(&mut username)?;
   Ok(username)
   //On peut même enchaîner derrière un ?
   //File::open("hello.txt")?.read_to_string(&mut username)?;
   //Ok(username)

}

//Et encore plus court fn read_username_from_file2() -> Result<String, io::Error> {

   fs::read_to_string("hello.txt")

}

//Fonctionne aussi avec un Option<T> (Some, None, tout ça) fn last_char_of_first_line(text: &str) -> Option<char> {

   //Ici next() renvoie un Option<str>
   //Va renvoyer None si il n'ya rien (sans prendre en compte
   //chars().last() et donc pas d'erreur), 
   //Va extraire le str et le filer à la méthode d'après (chars())
   //sinon
   text.lines().next()?.chars().last()

}

//On peut donc utiliser ? sur un Result ou une Option, mais //il ne va pas convertir l'un en l'autre : on doit respecter la //signature de la fonction. On peut alors utiliser ok() sur un result ou //ok_or() sur une option. // //BTW, une fonction main peut renvoyer des erreurs, que l'on peut d'ailleurs gérer //avec ?; si c'est le cas, l'exécutable renverra 0 si Ok et autre chose si erreur.


</syntaxhighlight>

Retour d'erreurs avec des Box<dyn>

Pour rappel, les Box<dyn T> servent à récupérer n'importe quel objet implémentant le trait T. Plutôt que de se baser sur un type, on se base sur un trait : c'est utile notamment dans les signatures de fonctions.

On peut utiliser cette mécanique pour gérer le retour d'erreur : dans une fonction qui renvoie un résultat, on peut parfois avoir plusieurs erreurs possibles, de plusieurs types; c'est embêtant car on ne peut en renvoyer qu'un seul. Cependant, toutes les erreurs implémentent le trait std::error. On peut donc se servir de ça pour renvoyer n'importe quel type d'erreur.

<syntaxhighlight lang="rust"> use std::error; use std::fmt; use std::num::ParseIntError;


  1. [derive(PartialEq, Debug)]

struct PositiveNonzeroInteger(u64);

  1. [derive(PartialEq, Debug)]

enum CreationError {

   Negative,
   Zero,

}

impl PositiveNonzeroInteger {

   fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> {
       match value {
           x if x < 0 => Err(CreationError::Negative),
           x if x == 0 => Err(CreationError::Zero),
           x => Ok(PositiveNonzeroInteger(x as u64)),
       }
   }

}

// This is required so that `CreationError` can implement `error::Error`. impl fmt::Display for CreationError {

   fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
       let description = match *self {
           CreationError::Negative => "number is negative",
           CreationError::Zero => "number is zero",
       };
       f.write_str(description)
   }

}

//Ici, mon erreur implémente le trait error impl error::Error for CreationError {}

//Ici, je peux récupérer plusieurs type d'erreur fn main() -> Result<(), Box<dyn error::Error>> {

   let pretend_user_input = "42";
   let x: i64 = pretend_user_input.parse()?;
   println!("output={:?}", PositiveNonzeroInteger::new(x)?);
   Ok(())

} </syntaxhighlight>