Rust Cheatsheet
Error Handling
Use this Rust reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
Result\<T, E\> Basics
The primary error type in Rust. Functions that can fail return Result<T, E>.
fn divide(a: f64, b: f64) -> Result<f64, String> { if b == 0.0 { Err("division by zero".to_string()) } else { Ok(a / b) } } match divide(10.0, 2.0) { Ok(result) => println!("Result: {}", result), Err(e) => println!("Error: {}", e), }
The ? Operator
Propagates errors up the call stack. Equivalent to match with early return Err(...).
use std::io; use std::fs::File; use std::io::Read; fn read_username(path: &str) -> Result<String, io::Error> { let mut f = File::open(path)?; // return Err if open fails let mut s = String::new(); f.read_to_string(&mut s)?; // return Err if read fails Ok(s) } // Chaining with ? fn read_and_parse(path: &str) -> Result<i32, Box<dyn std::error::Error>> { let s = std::fs::read_to_string(path)?; let n: i32 = s.trim().parse()?; // ParseIntError auto-converted Ok(n * 2) } // ? uses From to convert error types: // err? is equivalent to: // match err { Ok(v) => v, Err(e) => return Err(From::from(e)) }
? in main (requires main to return Result):
fn main() -> Result<(), Box<dyn std::error::Error>> { let content = std::fs::read_to_string("file.txt")?; println!("{}", content); Ok(()) }
? with Option (in functions returning Option):
fn first_double(v: &[i32]) -> Option<i32> { let first = v.first()?; // return None if empty Some(first * 2) }
Unwrapping (Quick and Dirty)
let n: i32 = "42".parse().unwrap(); // panics if Err let n: i32 = "42".parse().expect("not a number"); // panics with message let n: i32 = "42".parse().unwrap_or(0); // default on error let n: i32 = "42".parse().unwrap_or_else(|e| { eprintln!("{}", e); 0 }); let n: i32 = "abc".parse().unwrap_or_default(); // i32::default() = 0
Use unwrap/expect only in tests, prototypes, or when you know it cannot fail.
Result Methods
let ok: Result<i32, String> = Ok(42); let err: Result<i32, String> = Err("oops".to_string()); // Checking ok.is_ok() // true ok.is_err() // false // Extracting ok.ok() // Option<i32> — Some(42) ok.err() // Option<String> — None ok.as_ref() // Result<&i32, &String> ok.as_mut() // Result<&mut i32, &mut String> // Transforming success ok.map(|x| x * 2) // Ok(84) ok.and_then(|x| Ok(x + 1)) // Ok(43) — flatMap ok.and(Ok("new value")) // Ok("new value") if self is Ok // Transforming error err.map_err(|e| format!("!{}", e)) // Err("!oops") err.or(Ok(0)) // Ok(0) — fallback value err.or_else(|e| Err(e.len())) // Err(4) // Consuming ok.into_ok().unwrap_or(0) // i32 (nightly: into_ok()) ok.unwrap_or_else(|e| panic!("{}", e))
Defining Custom Error Types
Simple Error Enum
use std::fmt; use std::num::ParseIntError; #[derive(Debug)] enum AppError { ParseError(ParseIntError), DivisionByZero, InvalidInput(String), IoError(std::io::Error), } impl fmt::Display for AppError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { AppError::ParseError(e) => write!(f, "parse error: {}", e), AppError::DivisionByZero => write!(f, "division by zero"), AppError::InvalidInput(s) => write!(f, "invalid input: {}", s), AppError::IoError(e) => write!(f, "IO error: {}", e), } } } // Implement std::error::Error (required for ? coercions and Box<dyn Error>) impl std::error::Error for AppError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { AppError::ParseError(e) => Some(e), AppError::IoError(e) => Some(e), _ => None, } } } // Implement From for automatic ? conversion impl From<ParseIntError> for AppError { fn from(e: ParseIntError) -> Self { AppError::ParseError(e) } } impl From<std::io::Error> for AppError { fn from(e: std::io::Error) -> Self { AppError::IoError(e) } } fn process(s: &str) -> Result<i32, AppError> { let n: i32 = s.parse()?; // ParseIntError → AppError via From if n == 0 { return Err(AppError::DivisionByZero); } Ok(100 / n) }
Box\<dyn Error\> — Erasing Error Types
When you don't care about the specific error type:
use std::error::Error; fn run() -> Result<(), Box<dyn Error>> { let s = std::fs::read_to_string("data.txt")?; // io::Error let n: i32 = s.trim().parse()?; // ParseIntError println!("{}", n); Ok(()) }
Downside: you lose the ability to match on specific error variants.
thiserror Crate (Recommended for Libraries)
[dependencies]
thiserror = "1"use thiserror::Error; #[derive(Debug, Error)] enum MyError { #[error("io error: {0}")] Io(#[from] std::io::Error), #[error("parse error: {0}")] Parse(#[from] std::num::ParseIntError), #[error("invalid value {value}: {reason}")] InvalidValue { value: i32, reason: String }, #[error("not found")] NotFound, } fn parse_file(path: &str) -> Result<i32, MyError> { let s = std::fs::read_to_string(path)?; // io::Error → MyError::Io let n: i32 = s.trim().parse()?; // ParseIntError → MyError::Parse if n < 0 { return Err(MyError::InvalidValue { value: n, reason: "must be non-negative".into() }); } Ok(n) }
anyhow Crate (Recommended for Applications)
[dependencies]
anyhow = "1"use anyhow::{Result, Context, bail, ensure, anyhow}; fn read_number(path: &str) -> Result<i32> { let s = std::fs::read_to_string(path) .with_context(|| format!("failed to read {}", path))?; let n: i32 = s.trim().parse() .context("failed to parse as integer")?; ensure!(n > 0, "number must be positive, got {}", n); if n > 1000 { bail!("number {} is too large", n); } Ok(n) } // anyhow::Result<T> = Result<T, anyhow::Error> // .context() / .with_context() add context to any error // bail!() = return Err(anyhow!(...)) // ensure!(cond, msg) = if !cond { bail!(msg) } // Downcasting fn handle(e: anyhow::Error) { if let Some(io_err) = e.downcast_ref::<std::io::Error>() { println!("IO error: {}", io_err); } }
Panicking
For unrecoverable errors or programmer mistakes — not for recoverable errors.
panic!("something went terribly wrong"); panic!("index {} out of bounds (len {})", i, len); // Other ways to panic: unwrap() / expect() // on None or Err assert!(condition, "message"); assert_eq!(a, b); assert_ne!(a, b); unreachable!("should never reach here"); todo!("not implemented yet"); unimplemented!(); // Catch a panic (use sparingly — mainly for FFI or test harnesses) use std::panic; let result = panic::catch_unwind(|| { panic!("oops"); }); assert!(result.is_err());
Error Propagation Patterns
// Collecting results — fail on first error let strings = vec!["1", "2", "3", "abc", "5"]; let numbers: Result<Vec<i32>, _> = strings.iter() .map(|s| s.parse::<i32>()) .collect(); // Err(ParseIntError) — stops at "abc" // Collect all results, keeping both Ok and Err let results: Vec<Result<i32, _>> = strings.iter() .map(|s| s.parse::<i32>()) .collect(); // Partition into successes and failures let (successes, failures): (Vec<_>, Vec<_>) = strings.iter() .map(|s| s.parse::<i32>()) .partition(Result::is_ok); // Skip errors, keep successes let numbers: Vec<i32> = strings.iter() .filter_map(|s| s.parse().ok()) .collect(); // [1, 2, 3, 5] // Log errors, keep successes let numbers: Vec<i32> = strings.iter() .filter_map(|s| { s.parse().map_err(|e| eprintln!("parse error: {}", e)).ok() }) .collect();
Error Conversion Hierarchy
// From hierarchy: specific → general impl From<std::num::ParseIntError> for AppError { ... } impl From<std::io::Error> for AppError { ... } impl From<AppError> for Box<dyn std::error::Error> { ... } // auto via std // ? uses From::from to convert fn example() -> Result<(), AppError> { let _n: i32 = "abc".parse()?; // ParseIntError → AppError via From Ok(()) }
std::io Error Kinds
use std::io::{self, ErrorKind}; fn handle_io_error(e: io::Error) { match e.kind() { ErrorKind::NotFound => println!("file not found"), ErrorKind::PermissionDenied => println!("permission denied"), ErrorKind::ConnectionRefused => println!("connection refused"), ErrorKind::ConnectionReset => println!("connection reset"), ErrorKind::ConnectionAborted => println!("connection aborted"), ErrorKind::AddrInUse => println!("address in use"), ErrorKind::BrokenPipe => println!("broken pipe"), ErrorKind::AlreadyExists => println!("already exists"), ErrorKind::WouldBlock => println!("would block"), ErrorKind::InvalidInput => println!("invalid input"), ErrorKind::InvalidData => println!("invalid data"), ErrorKind::TimedOut => println!("timed out"), ErrorKind::UnexpectedEof => println!("unexpected EOF"), ErrorKind::OutOfMemory => println!("out of memory"), _ => println!("other: {}", e), } } // Create io errors let e = io::Error::new(ErrorKind::NotFound, "custom message"); let e = io::Error::from(ErrorKind::InvalidInput);