How to use ok_or to convert Option to Result

Convert an Option to a Result in Rust using the ok_or method by providing a default error value for None cases.

The bridge from maybe to error

You're building a configuration loader. You ask the parser for the database_url key. The parser hands you an Option<String>. Your downstream code expects a Result<String, ConfigError>. You can't pass the Option through. The types don't match. You need to turn that missing key into a proper error that the caller can handle. That's where ok_or steps in.

Option<T> tells you a value exists or doesn't. Result<T, E> tells you a value exists or an error occurred. They look similar, but they carry different information. Option hides the reason for failure. Result exposes it. ok_or converts an Option into a Result by supplying the missing error information. You provide the error value. ok_or wraps the Some in Ok and the None in Err with your error.

How it works

ok_or takes an error value as an argument. It checks the Option. If the option is Some(value), it returns Ok(value). The error argument is ignored. If the option is None, it returns Err(error). The error value you passed is moved into the Err variant.

fn main() {
    // Start with an Option that might hold a value.
    let maybe_num: Option<i32> = Some(42);
    
    // Convert to Result. If None, use the provided error string.
    // The type annotation helps the compiler infer the error type.
    let result: Result<i32, &str> = maybe_num.ok_or("Number is missing");
    
    // Handle the Result. Ok carries the value, Err carries the error.
    match result {
        Ok(n) => println!("Got {}", n),
        Err(e) => println!("Error: {}", e),
    }
}

The method doesn't clone the value. It moves the inner value into the Ok variant. The error value is also moved into the Err variant when None is encountered. This means ok_or consumes the Option and the error argument. You can't reuse the Option after calling ok_or.

Treat ok_or as the translator. You provide the error; it handles the wrapping.

Realistic usage in a chain

In real code, ok_or rarely sits alone. It usually appears at the end of an iterator chain or right before the ? operator. You use it to bridge the gap between a fallible lookup and a function that returns Result.

#[derive(Debug, Clone)]
struct User {
    id: u32,
    name: String,
}

/// Finds a user by ID, returning an error if not found.
fn find_user(users: &[User], id: u32) -> Result<User, String> {
    // .find returns Option<&User>.
    // .cloned converts Option<&User> to Option<User>.
    // .ok_or converts Option<User> to Result<User, String>.
    users.iter()
        .find(|u| u.id == id)
        .cloned()
        .ok_or(format!("User with id {} not found", id))
}

fn main() {
    let users = vec![User { id: 1, name: "Alice".into() }];
    
    // Use ? to propagate the error or extract the value.
    let user = find_user(&users, 1).expect("Should find Alice");
    println!("Found: {:?}", user);
}

The ? operator works seamlessly with ok_or. When you chain ok_or and then use ?, the Result propagates errors up the call stack. If the Option is Some, the value flows through. If it's None, the error you provided becomes the return value of the function. This pattern turns missing data into recoverable errors without verbose match blocks.

Don't fight the borrow checker here. Use cloned() or copied() to get an owned value before ok_or if your function needs to return an owned type.

The eager evaluation trap

ok_or has a hidden cost. The error argument is evaluated eagerly. This means the error value is constructed before ok_or runs. If the Option is Some, the error is built and then immediately dropped. You paid for the error construction even though you didn't need it.

fn expensive_error() -> String {
    // Simulate expensive work: allocation, I/O, computation.
    println!("Building error...");
    "Something went wrong".to_string()
}

fn main() {
    let opt = Some(42);
    
    // expensive_error() runs here, even though opt is Some.
    let result = opt.ok_or(expensive_error());
    
    // The error string is dropped immediately.
    println!("{:?}", result);
}

Running this code prints "Building error..." before printing the result. The error construction happened regardless of the Option's state. If expensive_error allocates memory or reads from a file, you're wasting resources.

The community convention is to use ok_or_else for expensive errors. ok_or_else takes a closure. The closure runs only when None is encountered. This is lazy evaluation. You pay for the error only when you need it.

fn main() {
    let opt = Some(42);
    
    // The closure runs only if opt is None.
    let result = opt.ok_or_else(|| {
        println!("Building error...");
        "Something went wrong".to_string()
    });
    
    println!("{:?}", result);
}

This version doesn't print "Building error...". The closure is skipped. ok_or_else saves work when success is common. Use ok_or for static strings, references, or cheap values. Use ok_or_else for dynamic errors, allocations, or anything that costs time.

Don't allocate errors you'll throw away. Reach for ok_or_else when the cost matters.

Type inference and compiler errors

ok_or returns a Result<T, E>. The compiler needs to know what E is. If you don't provide enough context, you'll get E0282 (type annotations needed). This happens when the error type can't be inferred from the return type or usage.

fn main() {
    let opt: Option<i32> = None;
    
    // Error: E0282 - type annotations needed.
    // The compiler doesn't know what E is.
    let result = opt.ok_or("error");
}

The compiler sees ok_or and knows it returns Result<i32, E>. It doesn't know what E should be. You need to tell it. You can add a type annotation to the variable.

fn main() {
    let opt: Option<i32> = None;
    
    // Explicitly state the Result type.
    let result: Result<i32, &str> = opt.ok_or("error");
}

Or you can let the function return type drive inference. If ok_or is the last expression in a function, the compiler infers E from the function signature.

fn get_value() -> Result<i32, &'static str> {
    let opt: Option<i32> = None;
    
    // E is inferred as &'static str from the return type.
    opt.ok_or("error")
}

The compiler won't guess your error type. Annotate the result or let the function signature do the work.

Choosing the right tool

Rust offers several ways to handle Option values. Picking the right one depends on what you want to do with the missing value.

Use ok_or when the error value is cheap to construct and you have a static error message or value ready.

Use ok_or_else when creating the error is expensive, involves allocation, or depends on runtime state that should only be accessed on failure.

Use unwrap_or when you want to provide a default value instead of failing, keeping the type as T rather than Result<T, E>.

Use expect when None is truly impossible and indicates a bug in your code, not a recoverable error.

Use map or and_then when you need to transform the value inside the Option before converting it to a Result.

Pick the tool that matches the cost of failure. Cheap error? ok_or. Expensive error? ok_or_else.

Where to go next