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

Rust src/main.rs
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"),
    }
}
Output
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

Rust src/main.rs
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),
    }
}
Output
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

Rust src/main.rs
fn main() {
    let result = divide(10.0, 0.0);
    let value = result.unwrap();
    println!("{}", value);
}
Output (cargo run)
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

Rust src/main.rs
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),
    }
}
Output
Failed at: cannot divide by zero
Note: ? 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.