Gérer les erreurs réparables avec Result

RustRustBeginner
Pratiquer maintenant

This tutorial is from open-source community. Access the source code

💡 Ce tutoriel est traduit par l'IA à partir de la version anglaise. Pour voir la version originale, vous pouvez cliquer ici

Introduction

Bienvenue dans Recoverable Errors With Result. Ce laboratoire est une partie du Rust Book. Vous pouvez pratiquer vos compétences Rust dans LabEx.

Dans ce laboratoire, nous apprenons à gérer les erreurs réparables avec l'énumération Result en Rust, qui nous permet d'interpréter et de répondre aux erreurs sans interrompre le programme.

Recoverable Errors with Result

La plupart des erreurs ne sont pas assez graves pour nécessiter l'arrêt total du programme. Parfois, lorsqu'une fonction échoue, c'est pour une raison que vous pouvez facilement interpréter et répondre. Par exemple, si vous essayez d'ouvrir un fichier et que cette opération échoue parce que le fichier n'existe pas, vous pouvez vouloir créer le fichier au lieu de terminer le processus.

Rappelez-vous de "Handling Potential Failure with Result" que l'énumération Result est définie comme ayant deux variantes, Ok et Err, comme suit :

enum Result<T, E> {
    Ok(T),
    Err(E),
}

Les paramètres de type génériques T et E : nous en parlerons en détail au chapitre 10. Ce dont vous devez vous souvenir pour l'instant, c'est que T représente le type de la valeur qui sera renvoyée dans un cas de réussite dans la variante Ok, et E représente le type de l'erreur qui sera renvoyée dans un cas d'échec dans la variante Err. Étant donné que Result a ces paramètres de type génériques, nous pouvons utiliser le type Result et les fonctions définies dessus dans de nombreuses situations différentes où la valeur de réussite et la valeur d'erreur que nous voulons renvoyer peuvent différer.

Appelons une fonction qui renvoie une valeur Result parce que la fonction peut échouer. Dans la liste 9-3, nous essayons d'ouvrir un fichier.

Nom du fichier : src/main.rs

use std::fs::File;

fn main() {
    let greeting_file_result = File::open("hello.txt");
}

Liste 9-3 : Ouvrir un fichier

Le type de retour de File::open est un Result<T, E>. Le paramètre de type générique T a été remplacé dans l'implémentation de File::open par le type de la valeur de réussite, std::fs::File, qui est un pointeur de fichier. Le type de E utilisé dans la valeur d'erreur est std::io::Error. Ce type de retour signifie que l'appel à File::open peut réussir et renvoyer un pointeur de fichier que nous pouvons lire ou écrire. L'appel de fonction peut également échouer : par exemple, le fichier peut ne pas exister, ou nous n'ayons pas les autorisations pour accéder au fichier. La fonction File::open doit avoir un moyen de nous dire si elle a réussi ou échoué et en même temps nous donner soit le pointeur de fichier soit des informations d'erreur. C'est exactement ce que l'énumération Result transporte.

Dans le cas où File::open réussit, la valeur dans la variable greeting_file_result sera une instance de Ok qui contient un pointeur de fichier. Dans le cas où cela échoue, la valeur dans greeting_file_result sera une instance de Err qui contient plus d'informations sur le type d'erreur qui s'est produite.

Nous devons ajouter au code de la liste 9-3 pour prendre des actions différentes selon la valeur renvoyée par File::open. La liste 9-4 montre une façon de gérer le Result en utilisant un outil de base, l'expression match que nous avons discutée au chapitre 6.

Nom du fichier : src/main.rs

use std::fs::File;

fn main() {
    let greeting_file_result = File::open("hello.txt");

    let greeting_file = match greeting_file_result {
        Ok(file) => file,
        Err(error) => {
            panic!("Problem opening the file: {:?}", error);
        }
    };
}

Liste 9-4 : Utilisation d'une expression match pour gérer les variantes Result qui pourraient être renvoyées

Notez que, comme l'énumération Option, l'énumération Result et ses variantes ont été importées dans la portée par le préambule, donc nous n'avons pas besoin de spécifier Result:: avant les variantes Ok et Err dans les branches match.

Lorsque le résultat est Ok, ce code renverra la valeur interne file de la variante Ok, et nous assignons ensuite cette valeur de pointeur de fichier à la variable greeting_file. Après le match, nous pouvons utiliser le pointeur de fichier pour lire ou écrire.

L'autre branche du match gère le cas où nous obtenons une valeur Err à partir de File::open. Dans cet exemple, nous avons choisi d'appeler la macro panic!. Si aucun fichier nommé hello.txt n'est présent dans notre répertoire actuel et que nous exécutons ce code, nous verrons la sortie suivante de la macro panic! :

thread'main' panicked at 'Problem opening the file: Os { code:
 2, kind: NotFound, message: "No such file or directory" }',
src/main.rs:8:23

Comme d'habitude, cette sortie nous dit exactement ce qui s'est mal passé.

Matching on Different Errors

Le code de la liste 9-4 provoquera une panique (panic!) peu importe pourquoi File::open a échoué. Cependant, nous souhaitons prendre des actions différentes pour différentes raisons d'échec. Si File::open a échoué parce que le fichier n'existe pas, nous voulons créer le fichier et renvoyer le pointeur vers le nouveau fichier. Si File::open a échoué pour toute autre raison - par exemple, parce que nous n'avons pas les autorisations pour ouvrir le fichier - nous souhaitons que le code provoque toujours une panique (panic!) de la même manière qu'il l'a fait dans la liste 9-4. Pour cela, nous ajoutons une expression match interne, comme montré dans la liste 9-5.

Nom du fichier : src/main.rs

use std::fs::File;
use std::io::ErrorKind;

fn main() {
    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!(
                        "Problem creating the file: {:?}",
                        e
                    ),
                }
            }
            other_error => {
                panic!(
                    "Problem opening the file: {:?}",
                    other_error
                );
            }
        },
    };
}

Liste 9-5 : Gérer différents types d'erreurs de manière différente

Le type de la valeur renvoyée par File::open à l'intérieur de la variante Err est io::Error, qui est une structure fournie par la bibliothèque standard. Cette structure a une méthode kind que nous pouvons appeler pour obtenir une valeur io::ErrorKind. L'énumération io::ErrorKind est fournie par la bibliothèque standard et a des variantes représentant les différents types d'erreurs qui peuvent résulter d'une opération io. La variante que nous voulons utiliser est ErrorKind::NotFound, qui indique que le fichier que nous essayons d'ouvrir n'existe pas encore. Ainsi, nous effectuons un match sur greeting_file_result, mais nous avons également un match interne sur error.kind().

La condition que nous voulons vérifier dans le match interne est de savoir si la valeur renvoyée par error.kind() est la variante NotFound de l'énumération ErrorKind. Si c'est le cas, nous essayons de créer le fichier avec File::create. Cependant, puisque File::create peut également échouer, nous avons besoin d'un deuxième bras dans l'expression match interne. Lorsque le fichier ne peut pas être créé, un message d'erreur différent est affiché. Le deuxième bras du match externe reste le même, de sorte que le programme provoque une panique pour toute erreur autre que l'erreur de fichier manquant.

Alternatives to Using match with Result<T, E>

C'est beaucoup de match! L'expression match est très utile mais également très primitive. Au chapitre 13, vous apprendrez à propos des closures, qui sont utilisées avec de nombreuses des méthodes définies sur Result<T, E>. Ces méthodes peuvent être plus concises que l'utilisation de match lorsqu'il s'agit de gérer des valeurs Result<T, E> dans votre code.

Par exemple, voici une autre façon d'écrire la même logique que celle montrée dans la liste 9-5, cette fois en utilisant des closures et la méthode unwrap_or_else :

// src/main.rs
use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let greeting_file = File::open("hello.txt").unwrap_or_else(|error| {
        if error.kind() == ErrorKind::NotFound {
            File::create("hello.txt").unwrap_or_else(|error| {
                panic!("Problem creating the file: {:?}", error);
            })
        } else {
            panic!("Problem opening the file: {:?}", error);
        }
    });
}

Bien que ce code ait le même comportement que la liste 9-5, il ne contient pas d'expressions match et est plus propre à lire. Revenez à cet exemple après avoir lu le chapitre 13 et consultez la documentation de la bibliothèque standard pour la méthode unwrap_or_else. De nombreuses autres de ces méthodes peuvent simplifier les expressions match imbriquées lorsqu'il s'agit de gérer les erreurs.

Shortcuts for Panic on Error: unwrap and expect

Utiliser match fonctionne assez bien, mais cela peut être un peu verbeux et ne communique pas toujours bien l'intention. Le type Result<T, E> a de nombreuses méthodes d'aide définies dessus pour effectuer diverses tâches plus spécifiques. La méthode unwrap est une méthode raccourcie implémentée de la même manière que l'expression match que nous avons écrite dans la liste 9-4. Si la valeur Result est la variante Ok, unwrap renverra la valeur à l'intérieur de Ok. Si le Result est la variante Err, unwrap appellera la macro panic! pour nous. Voici un exemple d'utilisation de unwrap :

Nom du fichier : src/main.rs

use std::fs::File;

fn main() {
    let greeting_file = File::open("hello.txt").unwrap();
}

Si nous exécutons ce code sans le fichier hello.txt, nous verrons un message d'erreur provenant de l'appel à panic! que la méthode unwrap effectue :

thread'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os {
code: 2, kind: NotFound, message: "No such file or directory" }',
src/main.rs:4:49

De manière similaire, la méthode expect nous permet également de choisir le message d'erreur de panic!. En utilisant expect au lieu de unwrap et en fournissant de bons messages d'erreur, vous pouvez exprimer votre intention et faciliter la recherche de la source d'une panique. La syntaxe de expect est la suivante :

Nom du fichier : src/main.rs

use std::fs::File;

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

Nous utilisons expect de la même manière que unwrap : pour renvoyer le pointeur de fichier ou appeler la macro panic!. Le message d'erreur utilisé par expect dans son appel à panic! sera le paramètre que nous passons à expect, plutôt que le message d'erreur par défaut que unwrap utilise. Voici à quoi cela ressemble :

thread'main' panicked at 'hello.txt should be included in this project: Os {
code: 2, kind: NotFound, message: "No such file or directory" }',
src/main.rs:5:10

Dans un code de production de qualité, la plupart des Rustaceans choisissent expect plutôt que unwrap et donnent plus de contexte sur pourquoi on s'attend à ce que l'opération réussisse toujours. Ainsi, si vos hypothèses se révèlent jamais fausses, vous avez plus d'informations pour le débogage.

Propagating Errors

Lorsque l'implémentation d'une fonction appelle une opération qui peut échouer, au lieu de gérer l'erreur dans la fonction elle-même, vous pouvez renvoyer l'erreur au code appelant afin qu'il puisse décider de ce qu'il faut faire. Cela s'appelle propager l'erreur et donne plus de contrôle au code appelant, où il peut y avoir plus d'informations ou de logique qui dictent la manière dont l'erreur devrait être gérée que ce que vous avez disponible dans le contexte de votre code.

Par exemple, la liste 9-6 montre une fonction qui lit un nom d'utilisateur à partir d'un fichier. Si le fichier n'existe pas ou ne peut pas être lu, cette fonction renverra ces erreurs au code qui a appelé la fonction.

Nom du fichier : src/main.rs

use std::fs::File;
use std::io::{self, Read};

1 fn read_username_from_file() -> Result<String, io::Error> {
  2 let username_file_result = File::open("hello.txt");

  3 let mut username_file = match username_file_result {
      4 Ok(file) => file,
      5 Err(e) => return Err(e),
    };

  6 let mut username = String::new();

  7 match username_file.read_to_string(&mut username) {
      8 Ok(_) => Ok(username),
      9 Err(e) => Err(e),
    }
}

Liste 9-6 : Une fonction qui renvoie des erreurs au code appelant en utilisant match

Cette fonction peut être écrite d'une manière beaucoup plus courte, mais nous allons commencer par la faire de manière manuelle pour explorer la gestion des erreurs ; à la fin, nous montrerons la manière plus courte. Regardons d'abord le type de retour de la fonction : Result<String, io::Error> [1]. Cela signifie que la fonction renvoie une valeur du type Result<T, E>, où le paramètre de type générique T a été remplacé par le type concret String et le type générique E a été remplacé par le type concret io::Error.

Si cette fonction réussit sans problème, le code qui appelle cette fonction recevra une valeur Ok qui contient une String - le username que cette fonction a lu à partir du fichier [8]. Si cette fonction rencontre des problèmes, le code appelant recevra une valeur Err qui contient une instance de io::Error qui contient plus d'informations sur les problèmes rencontrés. Nous avons choisi io::Error comme type de retour de cette fonction parce que c'est exactement le type de la valeur d'erreur renvoyée par les deux opérations que nous appelons dans le corps de cette fonction qui peuvent échouer : la fonction File::open [2] et la méthode read_to_string [7].

Le corps de la fonction commence par appeler la fonction File::open [2]. Ensuite, nous gérons la valeur Result avec un match similaire au match de la liste 9-4. Si File::open réussit, le pointeur de fichier dans la variable de modèle file [4] devient la valeur dans la variable mutable username_file [3] et la fonction continue. Dans le cas Err, au lieu d'appeler panic!, nous utilisons le mot clé return pour sortir de la fonction immédiatement et renvoyer la valeur d'erreur de File::open, maintenant dans la variable de modèle e, au code appelant comme valeur d'erreur de cette fonction [5].

Donc, si nous avons un pointeur de fichier dans username_file, la fonction crée ensuite une nouvelle String dans la variable username [6] et appelle la méthode read_to_string sur le pointeur de fichier dans username_file pour lire le contenu du fichier dans username [7]. La méthode read_to_string renvoie également un Result car elle peut échouer, même si File::open a réussi. Nous avons donc besoin d'un autre match pour gérer ce Result : si read_to_string réussit, alors notre fonction a réussi et nous renvoyons le nom d'utilisateur du fichier qui se trouve maintenant dans username encapsulé dans un Ok. Si read_to_string échoue, nous renvoyons la valeur d'erreur de la même manière que nous avons renvoyé la valeur d'erreur dans le match qui a géré la valeur de retour de File::open. Cependant, nous n'avons pas besoin de dire explicitement return, car c'est la dernière expression de la fonction [9].

Le code qui appelle ce code devra ensuite gérer la réception d'une valeur Ok qui contient un nom d'utilisateur ou d'une valeur Err qui contient un io::Error. C'est au code appelant de décider de ce qu'il faut faire avec ces valeurs. Si le code appelant reçoit une valeur Err, il pourrait appeler panic! et faire planter le programme, utiliser un nom d'utilisateur par défaut ou chercher le nom d'utilisateur à partir d'un autre emplacement que le fichier, par exemple. Nous n'avons pas assez d'informations sur ce que le code appelant essaye réellement de faire, donc nous propagons toutes les informations de réussite ou d'erreur vers le haut pour qu'il les gère de manière appropriée.

Ce modèle de propagation d'erreurs est si courant en Rust que Rust fournit l'opérateur point d'interrogation ? pour le simplifier.

A Shortcut for Propagating Errors: The? Operator

La liste 9-7 montre une implémentation de read_username_from_file qui a la même fonctionnalité que celle de la liste 9-6, mais cette implémentation utilise l'opérateur ?.

Nom du fichier : src/main.rs

use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
    let mut username_file = File::open("hello.txt")?;
    let mut username = String::new();
    username_file.read_to_string(&mut username)?;
    Ok(username)
}

Liste 9-7 : Une fonction qui renvoie des erreurs au code appelant en utilisant l'opérateur ?

Le ? placé après une valeur Result est défini pour fonctionner de manière presque identique aux expressions match que nous avons définies pour gérer les valeurs Result dans la liste 9-6. Si la valeur de la Result est un Ok, la valeur à l'intérieur de l'Ok sera renvoyée à partir de cette expression et le programme continuera. Si la valeur est un Err, l'Err sera renvoyé depuis la fonction entière comme si nous avions utilisé le mot clé return, de sorte que la valeur d'erreur soit propagée au code appelant.

Il y a une différence entre ce que fait l'expression match de la liste 9-6 et ce que fait l'opérateur ? : les valeurs d'erreur sur lesquelles l'opérateur ? est appelé passent par la fonction from, définie dans le trait From de la bibliothèque standard, qui est utilisée pour convertir des valeurs d'un type en un autre. Lorsque l'opérateur ? appelle la fonction from, le type d'erreur reçu est converti en le type d'erreur défini dans le type de retour de la fonction actuelle. Cela est utile lorsqu'une fonction renvoie un type d'erreur pour représenter toutes les manières dont une fonction peut échouer, même si certaines parties peuvent échouer pour de nombreuses raisons différentes.

Par exemple, nous pourrions modifier la fonction read_username_from_file de la liste 9-7 pour renvoyer un type d'erreur personnalisé nommé OurError que nous définissons. Si nous définissons également impl From<io::Error> for OurError pour construire une instance de OurError à partir d'un io::Error, alors les appels à l'opérateur ? dans le corps de read_username_from_file appelleront from et convertiront les types d'erreur sans avoir besoin d'ajouter plus de code à la fonction.

Dans le contexte de la liste 9-7, le ? à la fin de l'appel à File::open renverra la valeur à l'intérieur d'un Ok à la variable username_file. Si une erreur se produit, l'opérateur ? renverra immédiatement la fonction entière et donnera toute valeur Err au code appelant. La même chose s'applique au ? à la fin de l'appel à read_to_string.

L'opérateur ? élimine beaucoup de code répétitif et simplifie l'implémentation de cette fonction. Nous pourrions même raccourcir ce code encore plus en chaînant les appels de méthodes immédiatement après le ?, comme montré dans la liste 9-8.

Nom du fichier : src/main.rs

use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
    let mut username = String::new();

    File::open("hello.txt")?.read_to_string(&mut username)?;

    Ok(username)
}

Liste 9-8 : Chaînement d'appels de méthodes après l'opérateur ?

Nous avons déplacé la création de la nouvelle String dans username au début de la fonction ; cette partie n'a pas changé. Au lieu de créer une variable username_file, nous avons chaîné l'appel à read_to_string directement sur le résultat de File::open("hello.txt")?. Nous avons toujours un ? à la fin de l'appel à read_to_string, et nous renvoyons toujours une valeur Ok contenant username lorsque File::open et read_to_string réussissent plutôt que renvoyer des erreurs. La fonctionnalité est à nouveau la même que dans les listes 9-6 et 9-7 ; il s'agit juste d'une manière différente et plus ergonomique d'écrire le code.

La liste 9-9 montre une manière de le rendre encore plus court en utilisant fs::read_to_string.

Nom du fichier : src/main.rs

use std::fs;
use std::io;

fn read_username_from_file() -> Result<String, io::Error> {
    fs::read_to_string("hello.txt")
}

Liste 9-9 : Utilisation de fs::read_to_string au lieu d'ouvrir puis lire le fichier

La lecture d'un fichier dans une chaîne est une opération assez courante, donc la bibliothèque standard fournit la fonction pratique fs::read_to_string qui ouvre le fichier, crée une nouvelle String, lit le contenu du fichier, met le contenu dans cette String et la renvoie. Bien sûr, l'utilisation de fs::read_to_string ne nous donne pas l'occasion d'expliquer toute la gestion des erreurs, donc nous l'avons fait de la manière plus longue d'abord.

Where the? Operator Can Be Used

L'opérateur ? ne peut être utilisé que dans des fonctions dont le type de retour est compatible avec la valeur sur laquelle l'opérateur ? est utilisé. C'est parce que l'opérateur ? est défini pour effectuer un retour anticipé d'une valeur hors de la fonction, de la même manière que l'expression match que nous avons définie dans la liste 9-6. Dans la liste 9-6, le match utilisait une valeur Result, et le bras de retour anticipé renvoyait une valeur Err(e). Le type de retour de la fonction doit être un Result pour être compatible avec ce return.

Dans la liste 9-10, regardons l'erreur que nous obtiendrons si nous utilisons l'opérateur ? dans une fonction main dont le type de retour est incompatible avec le type de la valeur sur laquelle nous utilisons ?.

Nom du fichier : src/main.rs

use std::fs::File;

fn main() {
    let greeting_file = File::open("hello.txt")?;
}

Liste 9-10 : Tentative d'utilisation de ? dans la fonction main qui renvoie () ne compilera pas.

Ce code ouvre un fichier, ce qui peut échouer. L'opérateur ? suit la valeur Result renvoyée par File::open, mais cette fonction main a le type de retour (), pas Result. Lorsque nous compilons ce code, nous obtenons le message d'erreur suivant :

error[E0277]: the `?` operator can only be used in a function that returns
`Result` or `Option` (or another type that implements `FromResidual`)
 --> src/main.rs:4:48
  |
3 | / fn main() {
4 | |     let greeting_file = File::open("hello.txt")?;
  | |                                                ^ cannot use the `?`
operator in a function that returns `()`
5 | | }
  | |_- this function should return `Result` or `Option` to accept `?`
  |
  = help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not
implemented for `()`

Ce message d'erreur indique que nous ne sommes autorisés à utiliser l'opérateur ? que dans une fonction qui renvoie Result, Option ou un autre type qui implémente FromResidual.

Pour corriger l'erreur, vous avez deux choix. Un choix est de changer le type de retour de votre fonction pour qu'il soit compatible avec la valeur sur laquelle vous utilisez l'opérateur ?, tant que vous n'avez pas de restrictions empêchant cela. L'autre choix est d'utiliser un match ou l'une des méthodes de Result<T, E> pour gérer le Result<T, E> de la manière appropriée.

Le message d'erreur a également mentionné que ? peut également être utilisé avec des valeurs Option<T>. Comme pour utiliser ? sur Result, vous ne pouvez utiliser ? sur Option que dans une fonction qui renvoie une Option. Le comportement de l'opérateur ? lorsqu'il est appelé sur une Option<T> est similaire à son comportement lorsqu'il est appelé sur un Result<T, E> : si la valeur est None, le None sera renvoyé anticipément depuis la fonction à ce moment-là. Si la valeur est Some, la valeur à l'intérieur du Some est la valeur résultante de l'expression, et la fonction continue. La liste 9-11 a un exemple d'une fonction qui trouve le dernier caractère de la première ligne dans le texte donné.

fn last_char_of_first_line(text: &str) -> Option<char> {
    text.lines().next()?.chars().last()
}

Liste 9-11 : Utilisation de l'opérateur ? sur une valeur Option<T>

Cette fonction renvoie Option<char> parce qu'il est possible qu'il y ait un caractère là, mais il est également possible qu'il n'y en ait pas. Ce code prend l'argument text de type &str et appelle la méthode lines dessus, qui renvoie un itérateur sur les lignes du texte. Comme cette fonction veut examiner la première ligne, elle appelle next sur l'itérateur pour obtenir la première valeur de l'itérateur. Si text est une chaîne de caractères vide, cet appel à next renverra None, auquel cas nous utilisons ? pour arrêter et renvoyer None depuis last_char_of_first_line. Si text n'est pas une chaîne de caractères vide, next renverra une valeur Some contenant une tranche de chaîne de la première ligne dans text.

Le ? extrait la tranche de chaîne, et nous pouvons appeler chars sur cette tranche de chaîne pour obtenir un itérateur de ses caractères. Nous sommes intéressés par le dernier caractère dans cette première ligne, donc nous appelons last pour renvoyer le dernier élément de l'itérateur. Ceci est une Option parce que la première ligne peut être une chaîne de caractères vide ; par exemple, si text commence par une ligne vide mais contient des caractères sur d'autres lignes, comme dans "\nhi". Cependant, s'il y a un dernier caractère sur la première ligne, il sera renvoyé dans la variante Some. L'opérateur ? au milieu nous donne une manière concise d'exprimer cette logique, nous permettant d'implémenter la fonction en une seule ligne. Si nous ne pouvions pas utiliser l'opérateur ? sur Option, nous devrions implémenter cette logique en utilisant plus d'appels de méthodes ou une expression match.

Notez que vous pouvez utiliser l'opérateur ? sur un Result dans une fonction qui renvoie Result, et vous pouvez utiliser l'opérateur ? sur une Option dans une fonction qui renvoie Option, mais vous ne pouvez pas mélanger. L'opérateur ? ne convertit pas automatiquement un Result en une Option ou vice versa ; dans ces cas, vous pouvez utiliser des méthodes telles que la méthode ok sur Result ou la méthode ok_or sur Option pour effectuer la conversion explicitement.

Jusqu'à présent, toutes les fonctions main que nous avons utilisées renvoient (). La fonction main est spéciale parce qu'elle est le point d'entrée et de sortie d'un programme exécutable, et il y a des restrictions sur le type de retour qu'elle peut avoir pour que le programme se comporte comme prévu.

Heureusement, main peut également renvoyer un Result<(), E>. La liste 9-12 a le code de la liste 9-10, mais nous avons changé le type de retour de main pour qu'il soit Result<(), Box<dyn Error>> et ajouté une valeur de retour Ok(()) à la fin. Ce code compilera désormais.

Nom du fichier : src/main.rs

use std::error::Error;
use std::fs::File;

fn main() -> Result<(), Box<dyn Error>> {
    let greeting_file = File::open("hello.txt")?;

    Ok(())
}

Liste 9-12 : Changement de main pour renvoyer Result<(), E> permet l'utilisation de l'opérateur ? sur des valeurs Result.

Le type Box<dyn Error> est un objet de trait, dont nous parlerons dans "Utilisation d'objets de trait qui autorisent des valeurs de différents types". Pour l'instant, vous pouvez lire Box<dyn Error> comme signifiant "n'importe quel type d'erreur". Utiliser ? sur une valeur Result dans une fonction main avec le type d'erreur Box<dyn Error> est autorisé car cela permet tout Err valeur d'être renvoyée anticipément. Même si le corps de cette fonction main ne renverra jamais d'erreurs que du type std::io::Error, en spécifiant Box<dyn Error>, cette signature continuera d'être correcte même si plus de code renvoyant d'autres erreurs est ajouté au corps de main.

Lorsqu'une fonction main renvoie un Result<(), E>, l'exécutable se terminera avec une valeur de 0 si main renvoie Ok(()) et se terminera avec une valeur non nulle si main renvoie une valeur Err. Les exécutables écrits en C renvoient des entiers lorsqu'ils se terminent : les programmes qui se terminent avec succès renvoient l'entier 0, et les programmes qui rencontrent une erreur renvoient un entier différent de 0. Rust renvoie également des entiers depuis les exécutables pour être compatible avec cette convention.

La fonction main peut renvoyer tout type qui implémente le trait std::process::Termination, qui contient une fonction report qui renvoie un ExitCode. Consultez la documentation de la bibliothèque standard pour plus d'informations sur l'implémentation du trait Termination pour vos propres types.

Maintenant que nous avons discuté les détails de l'appel à panic! ou du retour de Result, revenons au sujet de la manière de décider laquelle est appropriée à utiliser dans chaque cas.

Summary

Félicitations! Vous avez terminé le laboratoire Recoverable Errors With Result. Vous pouvez pratiquer d'autres laboratoires dans LabEx pour améliorer vos compétences.