Skip to content

Error Handling

Rust divides errors into two fundamentally different categories, and handles each one differently.

Unrecoverable errors — bugs in your program that you did not anticipate and cannot sensibly continue from: an array index that is out of bounds, a failed assertion, or a logic invariant that should never be violated. Rust handles these with panic!, which unwinds the stack and terminates the thread.

Recoverable errors — situations where failure is expected and the caller should be able to react: a file might not exist, user input might not parse, or a network request might time out. Rust models these with the Result<T, E> enum, which forces every caller to explicitly handle or propagate the error.

This distinction is enforced by the type system. If a function can fail recoverably, its return type is Result<T, E>. If it can panic, there is no extra annotation — panics can happen anywhere, but they should be reserved for genuine programming bugs.

LessonTopic
Panicspanic!, .unwrap(), .expect(), when panicking is appropriate
ResultResult<T, E>, Ok/Err, matching, .unwrap_or, .unwrap_or_else
The ? OperatorEarly-return propagation, chaining, main returning Result
Custom ErrorsError enums, Display + Error traits, From conversions

The snippet below shows both error kinds side by side: a panic! that you would only call when the input is a logic error, and a Result-returning function that the caller must handle.

fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err(String::from("division by zero"))
} else {
Ok(a / b)
}
}
fn main() {
// Recoverable error: use Result
match divide(10, 2) {
Ok(result) => println!("10 / 2 = {}", result),
Err(e) => println!("Error: {}", e),
}
match divide(5, 0) {
Ok(result) => println!("5 / 0 = {}", result),
Err(e) => println!("Error: {}", e),
}
println!("Program continues normally after handling errors.");
}
Which Rust construct models a recoverable error?
What does panic! do in Rust?
Why does the Rust compiler force you to handle a Result?