Error Handling
Rust has no exceptions. A value that might be missing is an Option; an operation that might fail is a Result — both are ordinary enums the compiler forces you to actually deal with, instead of a try/catch you can forget to write.
Option<T>: a value that might not exist
fn find_user(id: i32) -> Option<String> { if id == 1 { Some(String::from("Priya")) } else { None } } fn main() { match find_user(1) { Some(name) => println!("Found: {}", name), None => println!("No user with that id"), } }
Found: Priya
Option<T> is either Some(value) or None — Rust's replacement for null. Crucially, an Option<String> and a plain String are different types, so the compiler won't let you accidentally use a possibly-missing value as if it were guaranteed to be there; you have to unwrap it somehow first, usually with match as above.
Result<T, E>: an operation that might fail
fn divide(a: f64, b: f64) -> Result<f64, String> { if b == 0.0 { Err(String::from("cannot divide by zero")) } else { Ok(a / b) } } fn main() { match divide(10.0, 2.0) { Ok(result) => println!("Result: {}", result), Err(e) => println!("Error: {}", e), } match divide(10.0, 0.0) { Ok(result) => println!("Result: {}", result), Err(e) => println!("Error: {}", e), } }
Result: 5 Error: cannot divide by zero
Result<T, E> is either Ok(value) on success or Err(error) on failure — this is how Rust represents fallible operations everywhere, from parsing a string to opening a file, instead of throwing an exception that could be raised from anywhere and caught anywhere else.
unwrap() panics — use it deliberately, not by default
fn main() { let result = divide(10.0, 0.0); let value = result.unwrap(); println!("{}", value); }
thread 'main' panicked at src/main.rs:3:25: called `Result::unwrap()` on an `Err` value: "cannot divide by zero"
.unwrap() grabs the value out of a Some/Ok, or panics immediately if it's a None/Err. It's genuinely useful in quick scripts and examples where a failure really should be fatal, but reaching for it automatically in real application code just trades a handled error for an unhandled crash — match, or the ? operator below, are usually the better default.
The ? operator: propagate an error upward
fn calculate() -> Result<f64, String> { let step1 = divide(10.0, 2.0)?; let step2 = divide(step1, 0.0)?; Ok(step2) } fn main() { match calculate() { Ok(v) => println!("Success: {}", v), Err(e) => println!("Failed at: {}", e), } }
Failed at: cannot divide by zero
? after a Result-returning call means "if this is Ok, unwrap it and keep going; if it's Err, return that error immediately from the current function." It only works inside a function that itself returns a compatible Result, but where it applies, it turns a chain of calls that could each fail into code that reads almost as cleanly as if nothing could go wrong — while still forcing every failure to be handled somewhere.