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
expectin 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.