Result
Recipe | Crates |
---|---|
Result |
Result
use std::fs::File; use std::io; use std::io::Read; use std::num::ParseIntError; // Result is a type that represents either success (Ok) or failure (Err). // pub enum Result<T, E> { // Ok(T), // Err(E), // } // Faillible functions like `File::open` return a `Result`... fn open_file(file_path: &str) { let open_result: Result<File, io::Error> = File::open(file_path); // You could handle their `Result` there and then... match open_result { Err(e) => eprintln!("An error occurred while opening the file: {}", e), Ok(file) => println!("Opened {:?}", file), } } // ...but most often you'll want to return early when encountering an error, // and propagate the error up the call stack. #[allow(clippy::question_mark)] fn read_file(file_path: &str) -> Result<String, io::Error> { let mut file = match File::open(file_path) { Ok(file) => file, Err(e) => return Err(e), }; // This said, having to use multiple `match` or `if let` is verbose // when chaining calls to faillible methods... let mut contents = String::new(); if let Err(e) = file.read_to_string(&mut contents) { Err(e) } else { Ok(contents) } } // Therefore, use the ? operator as a shortcut to return early // in case of an error. The following is equivalent to the previous function. fn read_file2(file_path: &str) -> Result<String, io::Error> { let mut file: File = File::open(file_path)?; // Note that file is of type `File`, not `io::Result<File> = Result<File, // io::Error>` let mut contents = String::new(); file.read_to_string(&mut contents)?; Ok(contents) } // You can even chain method calls immediately after the ?, e.g. // File::open(file_path)?.read_to_string(&mut contents)?; // You will often need to return one of multiple Result types. // You could create a custom error `enum` to do so: fn read_and_parse_file(file_path: &str) -> Result<i32, MyError> { let mut file = File::open(file_path)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; // `read_to_string` returns `Result<_, io::Error>` let number = contents.trim().parse()?; // `parse` returns `Result<_, std::num::ParseIntError>` Ok(number) } #[allow(dead_code)] #[derive(Debug)] enum MyError { Io(io::Error), Parse(ParseIntError), } // That custom error type must implement the `From` trait. impl From<io::Error> for MyError { fn from(err: io::Error) -> MyError { MyError::Io(err) } } impl From<ParseIntError> for MyError { fn from(err: ParseIntError) -> MyError { MyError::Parse(err) } } // The `thisError` crate provides a convenient derive macro // for the standard library’s `std::error::Error` trait. // Use when writing libraries. #[allow(dead_code)] #[derive(thiserror::Error, Debug)] enum MyError2 { #[error("Io error")] Io(#[from] io::Error), #[error("Parse error")] Parse(#[from] ParseIntError), } // A simpler method than returning a custom error type // may be to return a trait object (at the cost of opacity)... fn read_and_parse_file2( file_path: &str, ) -> Result<i32, Box<dyn std::error::Error>> { let mut file = File::open(file_path)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; let number = contents.trim().parse()?; Ok(number) } // ...but crates like `anyhow` can simplify error management a great deal... // `anyhow::Result<T>` is equivalent to `std::result::Result<T, anyhow::Error>`. fn read_and_parse_file3(file_path: &str) -> anyhow::Result<i32> { let mut file = File::open(file_path)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; let number = contents.trim().parse()?; Ok(number) } // A function without return value in truth returns `Unit`: // `fn func() {...}` is a shorthand for `fn func() -> () { ... }` // To convert it to a faillible function, return `Result<(), SomeErrorType>`. fn unit_return_value(file_path: &str) -> anyhow::Result<()> { let _i: i32 = read_and_parse_file3(file_path)?; println!("I don't return anything!"); // Do not forget to return an Result in the happy path. // The double parentheses are required. It is `Ok( () )`. Ok(()) } // `main()` can return a `Result`, more precisely a `Result<T, E> where T: // Termination, E: Debug)`. `Result<(), Box<dyn std::error::Error>>`, // `anyhow::Result<()>` are common choices. fn main() -> Result<(), Box<dyn std::error::Error>> { let file_path = "example.txt"; open_file(file_path); // Here we ignore the `Result` return values // to avoid returning early and exercise all the code. // You should rarely need to do this. let _ = read_file(file_path); let _ = read_file2(file_path); let _ = read_and_parse_file(file_path); let _ = read_and_parse_file2(file_path); let _ = read_and_parse_file3(file_path); let _ = unit_return_value(file_path); Ok(()) }
See also
[result: write (P1)](https://github.com/john-cd/rust_howto/issues/626)