Error Handling
Trigger and Handle Irrecoverable Panics
The panic!(...)
macro allows a program to terminate immediately and provide feedback to the caller of the program.
fn main() { panic!("Crash and burn"); }
panic!
is closely tied with the unwrap
method of both Option
and Result
enums. Both implementations call panic!
when they are set to None
or Err
variants.
// use std::str::FromStr; fn main() { let number_str = "42"; // `parse()` attempts to convert the string into a number. // This operation can fail if the string isn't a valid number, so it returns // a `Result<u32, ParseIntError>`. let number: u32 = number_str.parse().unwrap(); // `unwrap()` is called on the `Result` to extract the `u32` value. // - If the parsing is successful, the value is assigned to `number`. // - If the parsing fails, the program panics with an error message. println!("The number is: {}", number); }
Provide a Fallback Value with unwrap_or_else
use std::fs; use std::fs::File; use std::io::ErrorKind; fn main() { if !fs::exists("temp").unwrap() { fs::create_dir("temp").unwrap(); } let _greeting_file = File::open("temp/hello.txt").unwrap_or_else(|error| { if error.kind() == ErrorKind::NotFound { File::create("temp/hello.txt").unwrap_or_else(|error| { panic!("Problem creating the file: {:?}", error); }) } else { panic!("Problem opening the file: {:?}", error); } }); }
Return Recoverable Errors with Result
use std::io; use std::io::BufRead; fn main() { let mut my_cursor = io::Cursor::new(b"foo\nbar"); // A `Cursor` behaves like a `File` (but wraps a in-memory buffer). let mut my_string = String::new(); // `readline` reads all bytes until a newline (the 0xA byte) is reached, and // append them to the provided String. `readline` returns a `Result` // value, which is either success (`Ok`) or failure (`Err`). let result: Result<usize, std::io::Error> = my_cursor.read_line(&mut my_string); // We can process the Result as needed - here we just print the status. match result { Ok(0) => println!("End of file reached."), Ok(n) => println!("The total number of bytes read is {n}."), Err(ref e) => eprintln!("Error: {}", e), /* We could also retry `read_line` to attempt to recover from the error. */ } assert_eq!(my_string, "foo\n"); // Alternatively, if the error is truly not recoverable, we could cause the // program to crash if this instance of `Result` is an `Err` value. // `expect` panics and displays the message that you passed as an argument // to. result.expect("Failed to read line"); // You may also use `unwrap`, which panics if there is an error but does not // display a custom message. result.unwrap(); }
Propagate Errors with the ?
Operator
use std::fs::File; use std::io; use std::io::Read; fn read_username_from_file() -> Result<String, io::Error> { let mut username_file = File::open("temp/hello.txt")?; let mut username = String::new(); username_file.read_to_string(&mut username)?; Ok(username) } fn main() { if !std::fs::exists("temp").unwrap() { std::fs::create_dir("temp").unwrap(); } match read_username_from_file() { Ok(name) => println!("User name: {}", name), Err(err) => println!("Error: {}", err), } }
If the value of the Result is an Ok
, the value inside the Ok
will get returned from this expression, and the program will continue. If the value is an Err
, the Err
will be returned from the whole function, as if we had used the return
keyword, so the error value gets propagated to the calling code.
This error points out that we're only allowed to use the ?
operator in a function that returns Result
, Option
, or another type that implements std::ops::FromResidual
⮳.
Another example:
use std::error::Error; fn parse_port(s: &str) -> Result<u16, Box<dyn Error>> { // We need to use `Box<dyn Error>`, because the returned error type // cannot be determined during compile time: It will either // contain an instance of `std::num::ParseIntError` (from the parse // method, when parsing fails), or a string (when the port is // zero). Alternatively, you may use `anyhow::Result`. let port: u16 = s.parse()?; if port == 0 { Err(Box::from(format!("Invalid port: {}", port))) } else { Ok(port) } } fn main() { match parse_port("123") { Ok(port) => println!("Port: {}", port), Err(err) => panic!("{}", err), } }
std::io
defines the type alias type Result<T> = std::result::Result<T, std::io::Error>;
Handle Errors Correctly in main
std::io::Error
⮳ defined type implementing the std::error::Error
⮳ trait.
The below recipe will tell how long the system has been running by opening the Unix file /proc/uptime
and parse the content to get the first number. It returns the uptime, unless there is an error.
use std::fs::File; use std::io::Read; use anyhow::Result; use anyhow::anyhow; fn read_uptime() -> Result<u64> { let mut uptime = String::new(); File::open("/proc/uptime")?.read_to_string(&mut uptime)?; Ok(uptime .split('.') .next() .ok_or(anyhow!("Cannot parse uptime data"))? .parse()?) } fn main() { match read_uptime() { Ok(uptime) => println!("uptime: {} seconds", uptime), Err(err) => eprintln!("error: {}", err), }; }
Avoid Discarding Errors During Error Conversions
Uses reqwest::blocking
⮳ to query a random integer generator web service. Converts the string response into an integer.
// Add to your `Cargo.toml`: // reqwest = { version = "0.12.12", features = ["blocking"] } fn parse_response( response: reqwest::blocking::Response, ) -> anyhow::Result<u32> { let body = response.text()?; let body = body.trim(); // println!("Body: {body}"); let b = body.parse::<u32>()?; Ok(b) } fn main() -> anyhow::Result<()> { let url = "https://www.random.org/integers/?num=1&min=0&max=10&col=1&base=10&format=plain".to_string(); let response = reqwest::blocking::get(url)?; let random_value: u32 = parse_response(response)?; println!("A random number between 0 and 10: {}", random_value); Ok(()) }