Why are you tired of writing macros for Option to Result?
You're writing in Rust, and suddenly you realize: you have Option<T>, but you need Result<T, E>. It seems like a small thing. But if you're a beginner, your first thought is: "I probably need a macro!" Or you're already an experienced developer, but every time you write the same thing: match option { Some(v) => Ok(v), None => Err("error") }. Admit it, it gets tedious.
The Stack Overflow question "Is there a way to simplify converting an Option into a Result without a macro?" has gathered thousands of views. This is no coincidence: the problem is familiar to everyone who works with Rust. But the good news is that there is a solution, and it's surprisingly simple. In this tutorial, we'll cover all the methods, from basic to elegant, without a single macro.
We won't just give you the answer — we'll show you how to think like a Rust developer. Let's go!
What are Option and Result in Rust?
Before converting, let's make sure we're on the same page.
- Option<T> — a type that can be either
Some(T)(a value exists) orNone(no value). It is used when the absence of a value is a normal situation (e.g., searching for an element in a collection). - Result<T, E> — a type that can be either
Ok(T)(everything is fine) orErr(E)(an error occurred). It is used for operations that may fail (e.g., reading a file).
The problem: sometimes you have an Option, but according to the program's logic, the absence of a value is an error. That's where conversion is needed.
Basic method: match (but you already know this)
The most obvious and commonly used method by beginners is match. Yes, it works, but it's verbose:
fn get_user_name(id: u32) -> Option<String> { // ... some logic if id == 1 { Some("Alice".to_string()) } else { None }}
fn main() { let user_id = 2; let name = match get_user_name(user_id) { Some(name) => Ok(name), None => Err("User not found"), }; println!("{:?}", name); // Err("User not found")}This code is correct, but if you have dozens of such conversions in your project, you'll get tired of writing match. Moreover, it's easy to make a mistake and forget to handle None. We need something more concise.
The ok_or method: your new best friend
The Rust standard library provides the ok_or method for Option. It does exactly what we need: it converts Some(v) to Ok(v), and None to Err(error), where error is the value you pass.
fn main() { let user_id = 2; let name = get_user_name(user_id).ok_or("User not found"); println!("{:?}", name); // Err("User not found")}Just one line! But there's a nuance: ok_or takes an owned value. If you pass a string literal, as in the example, it works because &str implements Into<String>. But if you need to pass an error that is already stored somewhere, this can lead to unnecessary copying.
For such cases, there is ok_or_else — it takes a closure that is called only if the Option is None. This saves resources:
fn main() { let user_id = 2; let error_msg = format!("User with id {} not found", user_id); let name = get_user_name(user_id).ok_or_else(|| error_msg.clone()); // or even better: let name = get_user_name(user_id).ok_or_else(|| format!("User {} not found", user_id)); println!("{:?}", name);}Tip: use ok_or if the error message is a constant. Use ok_