SyntaxStudy
Sign Up
Rust Result<T, E> and Error Handling
Rust Beginner 1 min read

Result<T, E> and Error Handling

Rust has no exceptions. Instead, recoverable errors are represented as values of type `Result`, an enum with two variants: `Ok(T)` for success and `Err(E)` for failure. Every function that can fail returns `Result`, making the possibility of failure visible in the type signature and forcing callers to handle it. This design eliminates surprise exceptions and makes error paths as explicit as success paths. Handling a `Result` typically involves `match`, but the standard library also provides ergonomic methods: `.is_ok()`, `.is_err()`, `.unwrap()` (panics on `Err`), `.expect("msg")`, `.unwrap_or(default)`, `.map(|v| transform(v))`, `.map_err(|e| convert_err(e))`, and `.and_then(|v| fallible_fn(v))`. These combinators let you transform and chain results without nested `match` expressions. For unrecoverable errors — bugs that indicate the program is in an invalid state — Rust provides `panic!`. A panic unwinds the stack (by default) or aborts the process, and cannot be caught in normal code. Using `panic!` for logic errors (like index out of bounds) and `Result` for expected failure modes (like file not found) is the idiomatic separation of concerns in Rust error handling.
Example
use std::num::ParseIntError;

#[derive(Debug)]
enum AppError {
    ParseError(ParseIntError),
    NegativeNumber(i64),
    TooBig(i64),
}

impl From<ParseIntError> for AppError {
    fn from(e: ParseIntError) -> Self {
        AppError::ParseError(e)
    }
}

fn parse_and_validate(s: &str) -> Result<i64, AppError> {
    let n: i64 = s.trim().parse()?;   // ? converts ParseIntError via From
    if n < 0 {
        return Err(AppError::NegativeNumber(n));
    }
    if n > 1_000_000 {
        return Err(AppError::TooBig(n));
    }
    Ok(n)
}

fn double(s: &str) -> Result<i64, AppError> {
    let n = parse_and_validate(s)?;
    Ok(n * 2)
}

fn main() {
    let inputs = vec!["42", "  100  ", "-5", "999999999", "abc"];

    for input in inputs {
        match double(input) {
            Ok(v) => println!("double({input:?}) = {v}"),
            Err(AppError::ParseError(e)) => println!("{input:?} parse error: {e}"),
            Err(AppError::NegativeNumber(n)) => println!("{input:?} is negative: {n}"),
            Err(AppError::TooBig(n)) => println!("{input:?} is too big: {n}"),
        }
    }

    // Combinators
    let opt: Option<i64> = double("7").ok();
    println!("as option: {:?}", opt);

    let mapped = double("3").map(|v| v + 1);
    println!("mapped: {:?}", mapped);
}