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(())
}