Every I/O, parsing and network call returns Result; handling it fluently is the core of practical Rust.
Rust separates two kinds of failure. Unrecoverable errors are bugs (an index out of range, a broken invariant) and trigger a panic!, which unwinds the thread. Recoverable errors, like a missing file or bad input, are ordinary values of type `Result<T, E>`, an enum with Ok(T) and Err(E).
You can match on a Result, but the idiomatic way to pass errors up is the `?` operator. Inside a function that returns a Result, let text = fs::read_to_string(path)?; means: if Ok, unwrap the value; if Err, return it from the current function immediately. Before returning, ? converts the error with From::from, so a function returning Result<T, MyError> can use ? on any error type that MyError implements From for. ? also works on Option in functions returning Option.
main can return Result<(), E> where E: Debug; returning Err prints the error and exits with a non-zero code. A convenient quick signature is fn main() -> Result<(), Box<dyn std::error::Error>>, since any standard error converts into that box.
Useful methods: map_err to change the error type, ok() to turn it into an Option, unwrap_or_else, and_then, and is_ok()/is_err(). unwrap() and expect() panic on Err.
Result is marked #[must_use], so ignoring one produces a warning. If you really do not care, write let _ = do_thing(); to show it was deliberate.
A good rule: library code returns Result and lets the caller decide; only application code at the top decides to print, retry or exit.
Example
use std::num::ParseIntError;
fn parse_pair(s: &str) -> Result<(i32, i32), String> {
let (a, b) = s.split_once(',').ok_or("missing comma")?;
let a: i32 = a.trim().parse().map_err(|e: ParseIntError| e.to_string())?;
let b: i32 = b.trim().parse().map_err(|e: ParseIntError| e.to_string())?;
Ok((a, b))
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("{:?}", parse_pair("3, 4")); // Ok((3, 4))
println!("{:?}", parse_pair("3 4")); // Err("missing comma")
println!("{:?}", parse_pair("3, x")); // Err("invalid digit found in string")
let n: u8 = "200".parse()?; // ParseIntError converts into Box<dyn Error>
println!("n = {n}");
Ok(())
}Remember
- Panics are for bugs;
Resultis for failures the caller should handle ?returns early onErrand converts the error withFrommaincan returnResultto use?at the top level
Watch out for
- Using
.unwrap()on everyResultin library code, turning recoverable errors into crashes.