Rust Error Handling

Rust has no exceptions. Errors are values — you must handle them explicitly. Two types cover all cases:

Type Use for
Result<T, E> Operations that can fail (I/O, parsing, network)
Option<T> Values that might be absent

Result<T, E>

Rust
use std::fs;

fn read_file(path: &str) -> Result<String, std::io::Error> {
    fs::read_to_string(path)
}

match read_file("hello.txt") {
    Ok(content) => println!("{content}"),
    Err(e) => eprintln!("Error: {e}"),
}

The ? Operator

Propagate errors concisely — if the result is Err, return it immediately:

Rust
fn read_number(path: &str) -> Result<i32, Box<dyn std::error::Error>> {
    let content = fs::read_to_string(path)?;  // ? returns Err early
    let num = content.trim().parse::<i32>()?;
    Ok(num)
}

The ? operator is syntactic sugar for the common pattern:

Rust
// Without ?
let content = match fs::read_to_string(path) {
    Ok(c) => c,
    Err(e) => return Err(e.into()),
};

unwrap and expect

Quick-and-dirty for prototypes — panics on None/Err:

Rust
let content = fs::read_to_string("config.toml")
    .expect("config file missing");  // better error message than unwrap
Method Behaviour on error
unwrap() Panics with generic message
expect("msg") Panics with your message
? Returns the error to the caller
unwrap_or(default) Returns a default value
unwrap_or_else(|e| ...) Computes a default from the error

Tips

  • Use ? in production code — it propagates errors cleanly.
  • Use expect in tests and prototypes — it gives better panic messages.
  • Never use unwrap() in production — it gives unhelpful error messages.
  • Errors are values — handle them, do not ignore them.

Next: traits — shared behaviour across types.