Site

Result<T,E> — Rust Error Handling

Tutorial S4.0  •  Rust / Learn / StdLib

S4.0 What This Teaches

Result<T, E> is Rust's primary mechanism for expressing operations that can fail. Unlike exceptions, errors are values - the type system forces you to handle them. This tutorial covers:

S4.1 The Result Type

Result<T, E> is a standard library enum:
enum Result<T, E> {
    Ok(T),
    Err(E),
}
A function that can fail returns Result. The caller must inspect the variant before using the value - the compiler will not let you ignore it silently.

S4.2 Returning Result from a Function

use std::num::ParseIntError;

fn parse_positive(s: &str) -> Result<u32, String> {
    let n: i32 = s.parse().map_err(|e: ParseIntError| e.to_string())?;
    if n < 0 {
        Err(format!("{n} is negative"))
    } else {
        Ok(n as u32)
    }
}
The return type names both the success type (u32) and the error type (String). The body uses ? and returns Ok(...) or Err(...) explicitly.

S4.3 Handling Result with match

match is the most explicit way to handle both outcomes. The compiler requires both arms - you cannot accidentally use a value that might be an error:
match parse_positive("42") {
    Ok(n)  => println!("ok: {n}"),
    Err(e) => println!("err: {e}"),
}

S4.4 The ? Operator

Inside a function that returns Result, ? propagates an Err to the caller automatically. It eliminates repetitive match boilerplate when you want errors to bubble up:
fn double_positive(s: &str) -> Result<u32, String> {
    let n = parse_positive(s)?;   // returns Err to caller if parse_positive fails
    Ok(n * 2)
}
? can only appear in functions whose return type is Result (or Option). Using it in main requires main to return Result<(), E>.

S4.5 unwrap and expect

When you are certain a Result is Ok - typically in tests or demos - unwrap extracts the value or panics on Err:
let val = parse_positive("10").unwrap();
expect is the same but adds a custom message to the panic, making the failure site easier to diagnose:
let val = parse_positive("10").expect("should always parse");
Do not use unwrap in production code where Err is a realistic outcome.

S4.6 unwrap_or

unwrap_or returns the inner value on Ok, or a given default on Err:
let val = parse_positive("oops").unwrap_or(0);
println!("{val}");   // 0

S4.7 map, map_err, and and_then

These methods transform Result values without unwrapping them, enabling pipelines. map - applies a closure to the Ok value; passes Err through:
let doubled = parse_positive("8").map(|n| n * 2);
// Ok(16)
map_err - transforms the Err value; passes Ok through:
let result = parse_positive("xyz")
    .map_err(|e| format!("parse failed: {e}"));
// Err("parse failed: invalid digit found in string")
and_then - chains a second fallible operation; short-circuits on the first Err:
let result = parse_positive("20")
    .and_then(|n| if n < 100 { Ok(n) } else { Err(String::from("too large")) });
// Ok(20)

S4.8 Example - All Together

// Result - demonstrates Ok/Err, match, the ? operator, and common Result methods.

use std::num::ParseIntError;

fn parse_positive(s: &str) -> Result<u32, String> {
    let n: i32 = s.parse().map_err(|e: ParseIntError| e.to_string())?;
    if n < 0 {
        Err(format!("{n} is negative"))
    } else {
        Ok(n as u32)
    }
}

fn double_positive(s: &str) -> Result<u32, String> {
    let n = parse_positive(s)?;
    Ok(n * 2)
}

fn main() {
    // --- match on Result ---
    println!("--- match ---");
    for input in &["42", "-5", "abc"] {
        match parse_positive(input) {
            Ok(n)  => println!("ok: {n}"),
            Err(e) => println!("err: {e}"),
        }
    }

    // --- ? operator ---
    println!("--- ? operator ---");
    println!("{:?}", double_positive("7"));
    println!("{:?}", double_positive("bad"));

    // --- unwrap_or ---
    println!("--- unwrap_or ---");
    let val = parse_positive("oops").unwrap_or(0);
    println!("unwrap_or: {val}");

    // --- expect ---
    println!("--- expect ---");
    let val = parse_positive("10").expect("should parse");
    println!("expect: {val}");

    // --- map ---
    println!("--- map ---");
    let doubled = parse_positive("8").map(|n| n * 2);
    println!("map: {doubled:?}");

    // --- map_err ---
    println!("--- map_err ---");
    let result = parse_positive("xyz")
        .map_err(|e| format!("parse failed: {e}"));
    println!("map_err: {result:?}");

    // --- and_then: chain a second fallible operation ---
    println!("--- and_then ---");
    let result = parse_positive("20")
        .and_then(|n| if n < 100 { Ok(n) } else { Err(String::from("too large")) });
    println!("and_then: {result:?}");

    // --- is_ok / is_err ---
    println!("--- is_ok / is_err ---");
    println!("is_ok:  {}", parse_positive("5").is_ok());
    println!("is_err: {}", parse_positive("x").is_err());
}
Expected output:
--- match ---
ok: 42
err: -5 is negative
err: invalid digit found in string
--- ? operator ---
Ok(14)
Err("invalid digit found in string")
--- unwrap_or ---
unwrap_or: 0
--- expect ---
expect: 10
--- map ---
map: Ok(16)
--- map_err ---
map_err: Err("parse failed: invalid digit found in string")
--- and_then ---
and_then: Ok(20)
--- is_ok / is_err ---
is_ok:  true
is_err: true

S4.9 Exercise

Exercise
  • Write a function safe_sqrt(x: f64) -> Result<f64, String> that returns Err when x is negative and Ok(x.sqrt()) otherwise. Test it with positive, zero, and negative inputs.
  • Write a function read_two_ints(a: &str, b: &str) -> Result<i32, String> that parses both strings and returns their sum. Use ? to propagate any parse error.
  • Chain safe_sqrt and a second function safe_log(x: f64) -> Result<f64, String> (returns Err for x <= 0) using and_then. Compute log(sqrt(16.0)) and print the result.

S4.10 Common Mistakes

Using unwrap in code where Err is realistic

let content = std::fs::read_to_string("config.txt").unwrap();
// panics with an unhelpful message if the file is missing
Use ?, match, or .unwrap_or_else(|e| ...) to handle the error meaningfully.

Returning Err with the wrong error type

fn f() -> Result<u32, String> {
    let n: i32 = "x".parse()?;   // parse() returns ParseIntError, not String
    Ok(n as u32)
}
? requires the error type to convert into the function's declared error type via From. Add .map_err(|e| e.to_string()) before ?, or use a boxed error type for heterogeneous errors.

Ignoring a Result

std::fs::remove_file("tmp.txt");   // warning: unused Result
Rust warns when you discard a Result. If you truly do not care, write let _ = std::fs::remove_file("tmp.txt"); to silence the warning explicitly.

S4.11 Key Terms

TermMeaning
Result<T, E>Enum representing success (Ok(T)) or failure (Err(E))
Ok(T)The success variant carrying a value of type T
Err(E)The failure variant carrying an error of type E
?Propagates Err to the caller; only valid in Result-returning functions
unwrapExtracts the Ok value or panics on Err
expectLike unwrap with a custom panic message
unwrap_orReturns the Ok value or a given default on Err
mapTransforms the Ok value via a closure; passes Err through
map_errTransforms the Err value via a closure; passes Ok through
and_thenChains a second Result-returning closure on Ok; short-circuits on Err