Rust Error Handling

Real programs fail. Files do not exist. Networks go down. Users enter bad input. Rust requires you to handle errors explicitly. The Result type is how Rust handles operations that might fail.

What Is Result?

Result<T, E> is an enum with two variants:

enum Result<T, E> {
    Ok(T),    ← Success: contains a value of type T
    Err(E),   ← Failure: contains an error of type E
}

T is the type of the success value. E is the type of the error value.

The Package Delivery Diagram

Ok(value)   =  [ Package delivered successfully, item inside ]
Err(reason) =  [ Delivery failed, reason note inside ]

You always get a response. The question is which one.

A Function That Returns Result

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!("Answer: {}", result),
        Err(msg)   => println!("Error: {}", msg),
    }

    match divide(5.0, 0.0) {
        Ok(result) => println!("Answer: {}", result),
        Err(msg)   => println!("Error: {}", msg),
    }
}

Output:

Answer: 5
Error: Cannot divide by zero

Reading a File — A Common Result Example

use std::fs;

fn main() {
    let content = fs::read_to_string("notes.txt");

    match content {
        Ok(text) => println!("File contents: {}", text),
        Err(e)   => println!("Failed to read file: {}", e),
    }
}

The function fs::read_to_string returns Result<String, io::Error>. If the file exists, you get Ok(text). If it does not, you get Err(e).

The ? Operator — Propagate Errors Upward

When your function calls another function that returns Result, you often want to pass the error up to the caller instead of handling it immediately. The ? operator does this in one character.

Without ?:

fn read_file() -> Result<String, std::io::Error> {
    let content = match std::fs::read_to_string("notes.txt") {
        Ok(c)  => c,
        Err(e) => return Err(e),
    };
    Ok(content)
}

With ?:

fn read_file() -> Result<String, std::io::Error> {
    let content = std::fs::read_to_string("notes.txt")?;
    Ok(content)
}

The ? after an expression means: if this is Err, immediately return the error from this function. If it is Ok, unwrap the value and continue.

The Escalation Diagram

read_file()
    ↓
    fs::read_to_string()?
         ↓
         File missing → Err(e) → ? sends it back up to caller
         File found   → Ok(text) → content = text → continue

Useful Result Methods

unwrap()

Returns the success value or panics on error. Use only in tests or when failure truly cannot happen:

let text = fs::read_to_string("file.txt").unwrap();

expect(message)

Like unwrap(), but shows your custom message when it panics. Better for debugging:

let text = fs::read_to_string("file.txt").expect("Could not read file.txt");

unwrap_or(default)

Returns the success value or a fallback on error:

let text = fs::read_to_string("file.txt").unwrap_or(String::from("default content"));

is_ok() and is_err()

let r: Result<i32, &str> = Ok(5);
println!("{}", r.is_ok());   ← true
println!("{}", r.is_err());  ← false

Result vs Option

Type           Use when
----           --------
Option<T>     A value might not exist (no error reason needed)
Result<T,E>   An operation might fail (you want to know why)

Some(v) / None       ← Option
Ok(v) / Err(e)       ← Result

Chaining with map and and_then

Transform a Result value without a match:

let result: Result<i32, &str> = Ok(5);
let doubled = result.map(|v| v * 2);
println!("{:?}", doubled);  ← Ok(10)

Use and_then when the transformation itself can fail:

let result = "42".parse::<i32>()
    .and_then(|n| Ok(n * 2));
println!("{:?}", result);   ← Ok(84)

Quick Reference

Result<T, E>               ← Success or failure type
Ok(value)                  ← Successful result
Err(error)                 ← Failed result
result?                    ← Return Err early, or unwrap Ok
result.unwrap()            ← Get value, panic on Err
result.expect("msg")       ← Get value, panic with message on Err
result.unwrap_or(default)  ← Get value, return default on Err
result.is_ok()             ← true if Ok

Leave a Comment