Rust Result Type

Many actions can fail: opening a file, reading a number, or contacting a server. Rust represents the outcome with the Result type. Think of a parcel delivery: the package arrives intact, or the courier hands you a note explaining the problem. Result carries one of those two outcomes, and the compiler requires you to handle both.

The Shape of Result

Result is an enum with two variants.

enum Result<T, E> {
    Ok(T),
    Err(E),
}
            Result<T, E>
            /          \
        Ok(T)          Err(E)
     success value    error value

The letter T stands for the success type, and E stands for the error type. A function that parses a number returns Result<i32, ParseIntError>.

Returning a Result

The function below divides two numbers and refuses to divide by zero.

fn divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err(String::from("Cannot divide by zero"))
    } else {
        Ok(a / b)
    }
}

Handling a Result with match

A match expression opens the parcel and reacts to each outcome.

fn main() {
    match divide(10.0, 4.0) {
        Ok(answer) => println!("Answer: {answer}"),
        Err(message) => println!("Problem: {message}"),
    }

    match divide(5.0, 0.0) {
        Ok(answer) => println!("Answer: {answer}"),
        Err(message) => println!("Problem: {message}"),
    }
}
Answer: 2.5
Problem: Cannot divide by zero

Results in the Standard Library

Many standard functions return Result. Parsing text is a common case.

let good = "125".parse::<i32>();
let bad = "12x".parse::<i32>();

println!("{:?}", good);
println!("{:?}", bad);
Ok(125)
Err(ParseIntError { kind: InvalidDigit })

Opening a File

File operations return io::Result, a shortcut for a Result with an input-output error.

use std::fs::File;

fn main() {
    match File::open("notes.txt") {
        Ok(_) => println!("File opened"),
        Err(e) => println!("Could not open file: {e}"),
    }
}

Useful Helper Methods

Helper methods shorten common patterns.

MethodPurpose
is_ok() / is_err()Tests the variant without extracting
unwrap_or(value)Gives a fallback on Err
map(function)Transforms the Ok value
map_err(function)Transforms the Err value
and_then(function)Chains another step that may fail
ok()Converts Result into Option

Transforming Success Values

let doubled = "21".parse::<i32>().map(|n| n * 2);
println!("{:?}", doubled);   // Ok(42)

Chaining Steps

The and_then method runs a second fallible step only when the first one succeeded.

fn check_age(age: i32) -> Result<i32, String> {
    if age >= 18 { Ok(age) } else { Err(String::from("Too young")) }
}

let result = "20".parse::<i32>()
    .map_err(|e| e.to_string())
    .and_then(check_age);
println!("{:?}", result);
Ok(20)
 parse --Ok--> check_age --Ok--> final Ok
   |                |
  Err              Err
   |                |
   +-----> final Err

Ignoring a Result Is a Warning

Rust marks Result as must_use. Dropping one without looking at it triggers a compiler warning, because a hidden failure often turns into a hard-to-find bug.

Choosing Error Types

Small programs use String or a library error directly. Larger programs define custom error types that describe each failure precisely. Choose the simplest option that gives callers enough detail.

Result Compared with Option

TypeVariantsUse When
OptionSome, NoneA value may be absent, and absence is normal
ResultOk, ErrAn action may fail, and the reason matters

Common Mistakes

Calling unwrap on every Result removes the safety the type provides. Match on the value or use helper methods so failures produce friendly messages.

Returning a bare value from a function whose signature promises a Result causes a type error. Wrap success values in Ok(...) and failures in Err(...).

Mixing error types in one chain blocks compilation. Convert them with map_err, or use a shared error type.

Practice Ideas

  • Write a function that parses an age and returns an error for negative numbers.
  • Build a temperature converter that rejects values below absolute zero.
  • Combine two parse steps with and_then and print each outcome.

Reading Compiler Guidance

The compiler explains Result mistakes in plain language. Messages such as "mismatched types" usually mean a branch returns a bare value where an Ok wrapper belongs. Follow the arrow in the message to the exact line, and fix the wrapper first. Most Result errors disappear after that single change.

Key Points

  • Result has two variants: Ok for success and Err for failure.
  • Handle both variants with match or helper methods.
  • The compiler warns about ignored results.
  • Use map, map_err, and and_then to build clean pipelines.