Error Handling in Rust
In this lesson, you will learn how error handling works in Rust. Rust takes a unique and powerful approach to errors, helping developers write safe and reliable programs.
Unlike many languages that rely heavily on exceptions, Rust encourages handling errors explicitly at compile time.
Why Error Handling Matters
Errors are unavoidable in real-world programs. Files may not exist, network requests can fail, and user input may be invalid.
Rust’s error handling system ensures that these situations are handled safely and predictably.
Two Types of Errors in Rust
Rust categorizes errors into two main types:
- Recoverable errors – errors that can be handled gracefully
- Unrecoverable errors – serious errors that stop execution
Unrecoverable Errors with panic!
The panic! macro is used when something goes seriously wrong.
It stops the program immediately.
fn main() {
panic!("Something went wrong!");
}
When a panic occurs, Rust prints an error message and terminates the program.
Recoverable Errors with Result
Most errors in Rust are handled using the Result enum.
The Result type has two variants:
Ok(value)– represents successErr(error)– represents failure
Basic Result Example
Here is an example of a function that may fail:
fn divide(a: i32, b: i32) -> Result {
if b == 0 {
Err(String::from("Cannot divide by zero"))
} else {
Ok(a / b)
}
}
Handling Result with match
You can use a match statement to handle both success and failure cases.
match divide(10, 2) {
Ok(value) => println!("Result: {}", value),
Err(err) => println!("Error: {}", err),
}
Using unwrap()
The unwrap() method extracts the value from Ok
or panics if an error occurs.
This should only be used when you are sure the result is valid.
let result = divide(10, 2).unwrap();
Using expect()
The expect() method works like unwrap() but allows
you to provide a custom error message.
let result = divide(10, 0).expect("Division failed");
Propagating Errors with ?
The ? operator simplifies error propagation.
It returns the error to the caller automatically.
fn safe_divide(a: i32, b: i32) -> Result {
let result = divide(a, b)?;
Ok(result)
}
Custom Error Types
For larger programs, you may want to define your own error types.
enum MathError {
DivisionByZero,
}
Why Rust’s Error Handling Is Powerful
Rust forces developers to think about errors at compile time, which leads to more robust and predictable programs.
- No hidden exceptions
- Explicit error handling
- Safer production code
📝 Practice Exercises
Exercise 1
Write a function that returns a Result when dividing two numbers.
Exercise 2
Handle a Result using a match expression.
Exercise 3
Use the ? operator in a function.
Exercise 4
Create a custom error enum.
✅ Practice Answers
Answer 1
fn divide_safe(a: i32, b: i32) -> Result {
if b == 0 {
Err(String::from("Zero division"))
} else {
Ok(a / b)
}
}
Answer 2
match divide_safe(5, 0) {
Ok(v) => println!("{}", v),
Err(e) => println!("{}", e),
}
Answer 3
fn wrapper(a: i32, b: i32) -> Result {
let value = divide_safe(a, b)?;
Ok(value)
}
Answer 4
enum AppError {
InvalidInput,
}
What’s Next?
In the next lesson, you will learn about Iterators in Rust — a powerful way to process collections efficiently.