2024-10-02
004 - Error Handling
Learning Rust as a Pythonista: Error Handling
Rust makes failure explicit with Result and Option. That means more typing up front, but fewer hidden runtime surprises.
Why this matters for Python developers
- Python raises exceptions at runtime.
- Rust represents recoverable failures in the type system.
- The compiler helps you handle unhappy paths early.
Learning goals
By the end of this lesson, you should be able to:
- Use
Result<T, E>for operations that can fail. - Use
Option<T>when a value may be absent. - Propagate errors cleanly with
?.
Concepts in 5 minutes
Result<T, E>=Ok(value)orErr(error)Option<T>=Some(value)orNone?returns early withErr(...)when needed
Python baseline snippet
def divide(a, b):
try:
return a / b
except ZeroDivisionError:
return None
Rust equivalent snippets
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err("cannot divide by zero".to_string())
} else {
Ok(a / b)
}
}
fn find_value(values: &[i32], index: usize) -> Option<i32> {
values.get(index).copied()
}
One runnable end-to-end example
use std::num::ParseIntError;
#[derive(Debug)]
enum AppError {
Parse(ParseIntError),
InvalidConfig(String),
}
impl From<ParseIntError> for AppError {
fn from(value: ParseIntError) -> Self {
AppError::Parse(value)
}
}
fn parse_port(input: &str) -> Result<u16, AppError> {
let port: u16 = input.parse()?;
if port == 0 {
Err(AppError::InvalidConfig("port must be > 0".to_string()))
} else {
Ok(port)
}
}
fn maybe_timeout(ms: Option<u64>) -> u64 {
ms.unwrap_or(1_000)
}
fn main() {
// Result + ? + custom error
match parse_port("8080") {
Ok(port) => println!("port = {}", port),
Err(err) => println!("error: {:?}", err),
}
match parse_port("0") {
Ok(port) => println!("port = {}", port),
Err(err) => println!("error: {:?}", err),
}
// Option handling
println!("timeout = {} ms", maybe_timeout(Some(250)));
println!("timeout = {} ms", maybe_timeout(None));
}
Common mistakes
- Calling
unwrap()everywhere instead of handling errors intentionally. - Returning
Stringerrors for everything in larger apps (prefer typed errors). - Mixing
OptionandResultwithout clear intent.
Quick practice
- Change
parse_portto reject ports below1024. - Add
fn divide(a, b) -> Result<f64, AppError>and return a typed error on zero.
Recap
Use Result for failures and Option for missing values. Rust’s explicit model is a core reason production code is easier to reason about.
Next lesson: structs and enums.
Join the Journey Ahead!
If you're eager to continue this learning journey and stay updated with the latest insights, consider subscribing. By joining our mailing list, you'll receive notifications about new articles, tips, and resources to help you seamlessly pick up Rust by leveraging your Python skills.
Other articles in the series
- 000 - Learning Rust as a Pythonista: A Suggested Path
- 001 - Your First Rust Program
- 002 - Basic Syntax and Structure
- 003 - Ownership, Borrowing, and Lifetimes
- 005 - Structs and Enums
- 006 - Iterators and Closures
- 007 - Traits vs Duck Typing and Protocols
- 008 - Concurrency in Rust for Python Developers
- 009 - Async Concurrency with Tokio
- 010 - Pattern Matching in Rust
- 011 - Macros in Rust