Les erreurs récupérables avec Result
La plupart des erreurs ne sont pas suffisamment graves pour nécessiter l’arrêt complet du programme. Parfois, lorsqu’une fonction échoue, c’est pour une raison que vous pouvez facilement interpréter et traiter. Par exemple, si vous essayez d’ouvrir un fichier et que cette opération échoue parce que le fichier n’existe pas, vous voudrez peut-être créer le fichier plutôt que de terminer le processus.
Rappelez-vous, dans [« Gérer les échecs potentiels avec Result »][handle_failure] au chapitre 2, que l’enum Result est définie comme ayant deux variantes, Ok et Err, comme suit :
#![allow(unused)]
fn main() {
enum Result<T, E> {
Ok(T),
Err(E),
}
}
T et E sont des paramètres de type générique : nous aborderons les génériques plus en détail au chapitre 10. Ce que vous devez savoir pour l’instant, c’est que T représente le type de la valeur qui sera renvoyée en cas de succès dans la variante Ok, et E représente le type de l’erreur qui sera renvoyée en cas d’échec dans la variante Err. Comme Result possède ces paramètres de type générique, nous pouvons utiliser le type Result et les fonctions définies dessus dans de nombreuses situations différentes où la valeur de succès et la valeur d’erreur que nous voulons renvoyer peuvent différer.
Appelons une fonction qui renvoie une valeur Result parce que la fonction pourrait échouer. Dans l’encart 9-3, nous essayons d’ouvrir un fichier.
use std::fs::File;
fn main() {
let greeting_file_result = File::open("hello.txt");
}
Le type de retour de File::open est un Result<T, E>. Le paramètre générique T a été rempli par l’implémentation de File::open avec le type de la valeur de succès, std::fs::File, qui est un descripteur 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 pourrait réussir et renvoyer un descripteur de fichier à partir duquel nous pouvons lire ou écrire. L’appel de fonction pourrait également échouer : par exemple, le fichier pourrait ne pas exister, ou nous pourrions ne pas avoir la permission d’accéder au fichier. La fonction File::open a besoin d’un moyen de nous dire si elle a réussi ou échoué et en même temps de nous fournir soit le descripteur de fichier, soit les informations d’erreur. C’est exactement ce que l’enum Result transmet.
Dans le cas où File::open réussit, la valeur dans la variable greeting_file_result sera une instance de Ok contenant un descripteur de fichier. Dans le cas où elle échoue, la valeur dans greeting_file_result sera une instance de Err contenant plus d’informations sur le type d’erreur qui s’est produite.
Nous devons compléter le code de l’encart 9-3 pour effectuer différentes actions selon la valeur renvoyée par File::open. L’encart 9-4 montre une façon de gérer le Result en utilisant un outil de base, l’expression match que nous avons abordée au chapitre 6.
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:?}"),
};
}
match expression to handle the Result variants that might be returnedNotez que, comme l’enum Option, l’enum Result et ses variantes ont été importées dans la portée par le prelude, donc nous n’avons pas besoin de spécifier Result:: avant les variantes Ok et Err dans les branches du 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 descripteur de fichier à la variable greeting_file. Après le match, nous pouvons utiliser le descripteur de fichier pour lire ou écrire.
L’autre branche du match gère le cas où nous obtenons une valeur Err de File::open. Dans cet exemple, nous avons choisi d’appeler la macro panic!. S’il n’y a pas de fichier nommé hello.txt dans notre répertoire actuel et que nous exécutons ce code, nous verrons la sortie suivante de la macro panic! : console {{#include ../listings/ch09-error-handling/listing-09-04/output.txt}}
$ cargo run
Compiling error-handling v0.1.0 (file:///projects/error-handling)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.73s
Running `target/debug/error-handling`
thread 'main' panicked at src/main.rs:8:23:
Problem opening the file: Os { code: 2, kind: NotFound, message: "No such file or directory" }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Comme d’habitude, cette sortie nous indique exactement ce qui s’est mal passé.
Réagir selon les différentes erreurs
Le code de l’encart 9-4 va appeler panic! quelle que soit la raison de l’échec de File::open. Cependant, nous voulons effectuer différentes actions selon les différentes raisons de l’échec. Si File::open a échoué parce que le fichier n’existe pas, nous voulons créer le fichier et renvoyer le descripteur du nouveau fichier. Si File::open a échoué pour toute autre raison – par exemple, parce que nous n’avions pas la permission d’ouvrir le fichier – nous voulons toujours que le code appelle panic! de la même manière que dans l’encart 9-4. Pour cela, nous ajoutons une expression match interne, montrée dans l’encart 9-5.
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:?}"),
},
_ => {
panic!("Problem opening the file: {error:?}");
}
},
};
}
Le type de la valeur que File::open renvoie à l’intérieur de la variante Err est io::Error, qui est une structure fournie par la bibliothèque standard. Cette structure possède une méthode, kind, que nous pouvons appeler pour obtenir une valeur io::ErrorKind. L’enum io::ErrorKind est fournie par la bibliothèque standard et possède des variantes représentant les différents types d’erreurs qui peuvent résulter d’une opération d’io. La variante que nous voulons utiliser est ErrorKind::NotFound, qui indique que le fichier que nous essayons d’ouvrir n’existe pas encore. Donc, nous faisons un match sur greeting_file_result, mais nous avons aussi un match interne sur error.kind().
La condition que nous voulons vérifier dans le match interne est si la valeur renvoyée par error.kind() est la variante NotFound de l’enum ErrorKind. Si c’est le cas, nous essayons de créer le fichier avec File::create. Cependant, comme File::create pourrait également échouer, nous avons besoin d’une deuxième branche dans l’expression match interne. Lorsque le fichier ne peut pas être créé, un message d’erreur différent est affiché. La deuxième branche du match externe reste la même, de sorte que le programme panique pour toute erreur autre que l’erreur de fichier manquant.
Alternatives à l’utilisation de match avec Result<T, E>
Cela fait beaucoup de match ! L’expression match est très utile mais aussi très primitive. Au chapitre 13, vous apprendrez les fermetures (closures), qui sont utilisées avec beaucoup de méthodes définies sur Result<T, E>. Ces méthodes peuvent être plus concises que l’utilisation de match lors de la gestion 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 l’encart 9-5, cette fois en utilisant des fermetures et la méthode unwrap_or_else :
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 l’encart 9-5, il ne contient aucune expression match et est plus agréable à lire. Revenez à cet exemple après avoir lu le chapitre 13 et consultez la méthode unwrap_or_else dans la documentation de la bibliothèque standard. Beaucoup d’autres méthodes de ce type peuvent simplifier d’énormes expressions match imbriquées lorsque vous gérez des erreurs.
Raccourcis pour paniquer en cas d’erreur
L’utilisation de match fonctionne assez bien, mais elle peut être un peu verbeuse et ne communique pas toujours bien l’intention. Le type Result<T, E> possède de nombreuses méthodes utilitaires définies dessus pour effectuer diverses tâches plus spécifiques. La méthode unwrap est une méthode raccourci implémentée exactement comme l’expression match que nous avons écrite dans l’encart 9-4. Si la valeur Result est la variante Ok, unwrap renverra la valeur à l’intérieur du Ok. Si le Result est la variante Err, unwrap appellera la macro panic! pour nous. Voici un exemple de unwrap en action :
use std::fs::File;
fn main() {
let greeting_file = File::open("hello.txt").unwrap();
}
Si nous exécutons ce code sans fichier hello.txt, nous verrons un message d’erreur provenant de l’appel à panic! que la méthode unwrap effectue :
thread 'main' panicked at src/main.rs:4:49:
called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "No such file or directory" }
De la même manière, la méthode expect nous permet également de choisir le message d’erreur du panic!. Utiliser expect au lieu de unwrap et fournir de bons messages d’erreur peut transmettre votre intention et faciliter la recherche de l’origine d’un panic. La syntaxe de expect ressemble à ceci :
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 descripteur 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 par défaut de panic! qu’utilise unwrap. Voici à quoi cela ressemble :
thread 'main' panicked at src/main.rs:5:10:
hello.txt should be included in this project: Os { code: 2, kind: NotFound, message: "No such file or directory" }
Dans du code de qualité production, la plupart des Rustacés choisissent expect plutôt que unwrap et donnent plus de contexte sur la raison pour laquelle l’opération est censée toujours réussir. De cette façon, si vos hypothèses s’avèrent un jour incorrectes, vous avez plus d’informations à utiliser pour le débogage.
Propager les erreurs
Lorsque l’implémentation d’une fonction appelle quelque chose qui pourrait échouer, au lieu de gérer l’erreur au sein de la fonction elle-même, vous pouvez renvoyer l’erreur au code appelant afin qu’il puisse décider quoi faire. C’est ce qu’on appelle propager l’erreur, et cela donne plus de contrôle au code appelant, là où il pourrait y avoir plus d’informations ou de logique dictant comment l’erreur devrait être gérée que ce dont vous disposez dans le contexte de votre code.
Par exemple, l’encart 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.
#![allow(unused)]
fn main() {
use std::fs::File;
use std::io::{self, Read};
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),
}
}
}
matchCette fonction peut être écrite de manière beaucoup plus concise, mais nous allons commencer par faire beaucoup de choses manuellement afin d’explorer la gestion des erreurs ; à la fin, nous montrerons la manière plus courte. Examinons d’abord le type de retour de la fonction : Result<String, io::Error>. Cela signifie que la fonction renvoie une valeur du type Result<T, E>, où le paramètre générique T a été rempli avec le type concret String et le type générique E a été rempli avec le type concret io::Error.
Si cette fonction réussit sans aucun problème, le code qui appelle cette fonction recevra une valeur Ok contenant une String – le username que cette fonction a lu dans le fichier. Si cette fonction rencontre un problème, le code appelant recevra une valeur Err contenant une instance de io::Error qui contient plus d’informations sur la nature des problèmes. Nous avons choisi io::Error comme type de retour de cette fonction parce que c’est justement le type de la valeur d’erreur renvoyée par les deux opérations que nous appelons dans le corps de cette fonction et qui pourraient échouer : la fonction File::open et la méthode read_to_string.
Le corps de la fonction commence par appeler la fonction File::open. Ensuite, nous gérons la valeur Result avec un match similaire au match de l’encart 9-4. Si File::open réussit, le descripteur de fichier dans la variable de motif file devient la valeur dans la variable mutable username_file et la fonction continue. Dans le cas Err, au lieu d’appeler panic!, nous utilisons le mot-clé return pour sortir prématurément de la fonction et transmettre la valeur d’erreur de File::open, maintenant dans la variable de motif e, au code appelant comme valeur d’erreur de cette fonction.
Donc, si nous avons un descripteur de fichier dans username_file, la fonction crée ensuite une nouvelle String dans la variable username et appelle la méthode read_to_string sur le descripteur de fichier dans username_file pour lire le contenu du fichier dans username. La méthode read_to_string renvoie également un Result car elle pourrait é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, enveloppé 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 gérait 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.
Le code qui appelle ce code devra ensuite gérer soit une valeur Ok contenant un nom d’utilisateur, soit une valeur Err contenant un io::Error. C’est au code appelant de décider quoi faire avec ces valeurs. Si le code appelant obtient 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 ailleurs que dans un fichier, par exemple. Nous n’avons pas assez d’informations sur ce que le code appelant essaie réellement de faire, donc nous propageons toutes les informations de succès ou d’erreur vers le haut pour qu’il les gère de manière appropriée.
Ce patron de propagation des erreurs est tellement courant en Rust que Rust fournit l’opérateur point d’interrogation ? pour faciliter les choses.
Le raccourci de l’opérateur ?
L’encart 9-7 montre une implémentation de read_username_from_file qui à la même fonctionnalité que dans l’encart 9-6, mais cette implémentation utilise l’opérateur ?.
#![allow(unused)]
fn main() {
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)
}
}
? operatorLe ? placé après une valeur Result est défini pour fonctionner presque de la même manière que les expressions match que nous avons définies pour gérer les valeurs Result dans l’encart 9-6. Si la valeur du Result est un Ok, la valeur à l’intérieur du Ok sera renvoyée par cette expression et le programme continuera. Si la valeur est un Err, le Err sera renvoyé par la fonction entière comme si nous avions utilisé le mot-clé return, de sorte que la valeur d’erreur est propagée au code appelant.
Il y à une différence entre ce que fait l’expression match de l’encart 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 dans le type d’erreur défini dans le type de retour de la fonction courante. C’est utile lorsqu’une fonction renvoie un seul type d’erreur pour représenter toutes les façons 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 l’encart 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 du code supplémentaire à la fonction.
Dans le contexte de l’encart 9-7, le ? à la fin de l’appel à File::open renverra la valeur à l’intérieur d’un Ok dans la variable username_file. Si une erreur se produit, l’opérateur ? sortira prématurément de la fonction entière et transmettra 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 rend l’implémentation de cette fonction plus simple. Nous pourrions même raccourcir davantage ce code en enchaînant les appels de méthode immédiatement après le ?, comme montré dans l’encart 9-8.
#![allow(unused)]
fn main() {
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)
}
}
? operatorNous 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 enchaî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 tous les deux, plutôt que de renvoyer des erreurs. La fonctionnalité est à nouveau la même que dans les encarts 9-6 et 9-7 ; c’est simplement une manière différente et plus ergonomique de l’écrire.
L’encart 9-9 montre un moyen de rendre cela encore plus court en utilisant fs::read_to_string.
#![allow(unused)]
fn main() {
use std::fs;
use std::io;
fn read_username_from_file() -> Result<String, io::Error> {
fs::read_to_string("hello.txt")
}
}
fs::read_to_string instead of opening and then reading the fileLire un fichier dans une chaîne de caractères est une opération assez courante, c’est pourquoi 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, place le contenu dans cette String et la renvoie. Bien sûr, utiliser fs::read_to_string ne nous donne pas l’occasion d’expliquer toute la gestion des erreurs, c’est pourquoi nous l’avons fait de la manière longue d’abord.
Où utiliser l’opérateur ?
L’opérateur ? ne peut être utilisé que dans les fonctions dont le type de retour est compatible avec la valeur sur laquelle le ? 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 l’encart 9-6. Dans l’encart 9-6, le match utilisait une valeur Result, et la branche 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 l’encart 9-10, examinons l’erreur que nous obtiendrons si nous utilisons l’opérateur ? dans une fonction main avec un type de retour incompatible avec le type de la valeur sur laquelle nous utilisons ?.
use std::fs::File;
fn main() {
let greeting_file = File::open("hello.txt")?;
}
? in the main function that returns () won’t compile.Ce code ouvre un fichier, ce qui pourrait échouer. L’opérateur ? suit la valeur Result renvoyée par File::open, mais cette fonction main à le type de retour (), pas Result. Lorsque nous compilons ce code, nous obtenons le message d’erreur suivant : console {{#include ../listings/ch09-error-handling/listing-09-10/output.txt}}
$ cargo run
Compiling error-handling v0.1.0 (file:///projects/error-handling)
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() {
| --------- this function should return `Result` or `Option` to accept `?`
4 | let greeting_file = File::open("hello.txt")?;
| ^ cannot use the `?` operator in a function that returns `()`
|
help: consider adding return type
|
3 ~ fn main() -> Result<(), Box<dyn std::error::Error>> {
4 | let greeting_file = File::open("hello.txt")?;
5 + Ok(())
|
For more information about this error, try `rustc --explain E0277`.
error: could not compile `error-handling` (bin "error-handling") due to 1 previous error
Cette 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 consiste à modifier le type de retour de votre fonction pour le rendre compatible avec la valeur sur laquelle vous utilisez l’opérateur ?, tant qu’il n’y a pas de restrictions qui l’empêchent. L’autre choix consiste à 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 mentionnait également que ? peut être utilisé avec des valeurs Option<T>. Comme avec l’utilisation de ? 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 un Option<T> est similaire à son comportement lorsqu’il est appelé sur un Result<T, E> : si la valeur est None, le None sera renvoyé prématurément par 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. L’encart 9-11 contient un exemple de 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()
}
fn main() {
assert_eq!(
last_char_of_first_line("Hello, world
How are you today?"),
Some('d')
);
assert_eq!(last_char_of_first_line(""), None);
assert_eq!(last_char_of_first_line("
hi"), None);
}
? operator on an Option<T> valueCette fonction renvoie Option<char> parce qu’il est possible qu’il y ait un caractère, mais il est aussi possible qu’il n’y en ait pas. Ce code prend l’argument de tranche de chaîne text et appelle la méthode lines dessus, qui renvoie un itérateur sur les lignes de la chaîne. 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 la chaîne vide, cet appel à next renverra None, auquel cas nous utilisons ? pour nous arrêter et renvoyer None depuis last_char_of_first_line. Si text n’est pas la chaîne vide, next renverra une valeur Some contenant une tranche de chaîne de la première ligne de text.
Le ? extrait la tranche de chaîne, et nous pouvons appeler chars sur cette tranche de chaîne pour obtenir un itérateur sur ses caractères. Nous nous intéressons au dernier caractère de cette première ligne, donc nous appelons last pour renvoyer le dernier élément de l’itérateur. C’est une Option parce qu’il est possible que la première ligne soit la chaîne vide ; par exemple, si text commence par une ligne vide mais contient des caractères sur d’autres lignes, comme dans " hi". Cependant, s’il y à 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éthode 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 les deux. L’opérateur ? ne convertira pas automatiquement un Result en Option ou vice versa ; dans ces cas, vous pouvez utiliser des méthodes comme 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 car elle est le point d’entrée et de sortie d’un programme exécutable, et il y à des restrictions sur ce que son type de retour peut être pour que le programme se comporte comme prévu.
Heureusement, main peut également renvoyer un Result<(), E>. L’encart 9-12 reprend le code de l’encart 9-10, mais nous avons changé le type de retour de main en Result<(), Box<dyn Error>> et ajouté une valeur de retour Ok(()) à la fin. Ce code compilera maintenant.
use std::error::Error;
use std::fs::File;
fn main() -> Result<(), Box<dyn Error>> {
let greeting_file = File::open("hello.txt")?;
Ok(())
}
main to return Result<(), E> allows the use of the ? operator on Result values.in Chapter 18. For now, you can read Box<dyn Error> to mean “any kind of error.” Using ? on a Result value in a main function with the error type Box<dyn Error> is allowed because it allows any Err value to be returned early. Even though the body of this main function will only ever return errors of type std::io::Error, by specifying Box<dyn Error>, this signature will continue to be correct even if more code that returns other errors is added to the body of main. –> Le type Box<dyn Error> est un objet trait, dont nous parlerons dans [« Utiliser des objets trait pour abstraire les comportements partagés »][trait-objects] au chapitre 18. 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 à n’importe quelle valeur Err d’être renvoyée prématurément. Même si le corps de cette fonction main ne renverra jamais que des erreurs de type std::io::Error, en spécifiant Box<dyn Error>, cette signature restera correcte même si du code supplémentaire renvoyant d’autres erreurs est ajouté au corps de main.
Lorsqu’une fonction main renvoie un Result<(), E>, l’exécutable se terminera avec la valeur 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 échouent renvoient un entier autre que 0. Rust renvoie également des entiers depuis les exécutables pour être compatible avec cette convention.
La fonction main peut retourner n’importe quel type qui implémente le trait std::process::Termination, qui contient une fonction report retournant 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 abordé les détails de l’appel à panic! ou du renvoi de Result, revenons au sujet de la façon de décider lequel est approprié dans quels cas.