Fix "the `?` operator can only be used in a function that returns Result or Option" in Rust

beginner๐Ÿฆ€ Rust2026-03-17| Rust 1.13+ (any OS: Linux, macOS, Windows) โ€” triggered whenever `?` is used in a function whose return type is not `Result` or `Option`

Error Message

error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
#rust#error-handling#result#option#question-mark-operator

The scenario

You're refactoring some file I/O code, you slap a ? on a File::open() call to skip the verbose .unwrap(), and Rust immediately fires back:

error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
  --> src/main.rs:3:29
   |
3  |     let f = File::open("data.txt")?;
   |                                   ^ cannot use the `?` operator in a function that returns `()`
   |
   = help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not implemented for `()`

Your function returns () โ€” nothing. The ? operator needs somewhere to propagate the error. No Result in scope means nowhere for it to go.

Why this happens

The ? operator is syntactic sugar for an early return on error. Writing some_fn()? desugars to roughly:

match some_fn() {
    Ok(val) => val,
    Err(e) => return Err(e.into()),  // early return
}

That return Err(e) requires the enclosing function to return a Result. If it doesn't, there's no valid return path. The same logic applies to Option: ? on an Option returns None early, so the function must return Option too.

Fix 1: Change main() to return Result

main() is where this error bites most often. Add a return type and a final Ok(()):

use std::fs::File;
use std::io::Read;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut f = File::open("data.txt")?;
    let mut contents = String::new();
    f.read_to_string(&mut contents)?;
    println!("{}", contents);
    Ok(())
}

Box<dyn std::error::Error> is the catch-all โ€” it accepts any error that implements the Error trait. Good enough for most binaries and one-off scripts.

If you use the anyhow crate, it's even cleaner:

use anyhow::Result;

fn main() -> Result<()> {
    let contents = std::fs::read_to_string("data.txt")?;
    println!("{}", contents);
    Ok(())
}

Fix 2: Change a regular function's return type

Any function can use ? โ€” it just needs the right return type. Here's the before and after:

// Before โ€” compiler error
fn load_config() {
    let contents = std::fs::read_to_string("config.toml")?;
    // ...
}

// After โ€” compiles
fn load_config() -> Result<String, std::io::Error> {
    let contents = std::fs::read_to_string("config.toml")?;
    Ok(contents)
}

Dealing with multiple error types in one function? A boxed error saves you from writing manual conversions between them:

fn load_and_parse() -> Result<MyConfig, Box<dyn std::error::Error>> {
    let contents = std::fs::read_to_string("config.toml")?;
    let config: MyConfig = toml::from_str(&contents)?;  // different error type, no problem
    Ok(config)
}

Fix 3: Using ? inside closures

Closures are trickier. This won't compile:

let results: Vec<_> = paths.iter().map(|p| {
    let content = std::fs::read_to_string(p)?;  // error here
    content
}).collect();

The closure's inferred return type is String, not Result<String, _>. Make the closure return Result explicitly, then collect into a Result<Vec> โ€” this fails fast on the first error:

let results: Result<Vec<_>, _> = paths.iter().map(|p| {
    std::fs::read_to_string(p)
}).collect();

match results {
    Ok(contents) => { /* all files read */ }
    Err(e) => eprintln!("Failed: {}", e),
}

Need to skip failures silently instead? Use .ok() with filter_map:

let contents: Vec<String> = paths.iter()
    .filter_map(|p| std::fs::read_to_string(p).ok())
    .collect();

Fix 4: Mixing Result and Option

Mixing Result and Option in one function trips people up. They're different types โ€” you can't use ? on both without conversion:

use std::collections::HashMap;

// Error โ€” function returns Result but ? is used on an Option
fn get_value(map: &HashMap<String, String>, key: &str) -> Result<String, String> {
    let val = map.get(key)?;  // Option, not Result!
    Ok(val.clone())
}

Convert Option to Result with .ok_or():

fn get_value(map: &HashMap<String, String>, key: &str) -> Result<String, String> {
    let val = map.get(key).ok_or_else(|| format!("key '{}' not found", key))?;
    Ok(val.clone())
}

Flip direction โ€” function returns Option, but you have a Result โ€” use .ok() to discard the error:

fn try_read(path: &str) -> Option<String> {
    std::fs::read_to_string(path).ok()
}

Verify the fix

Before doing a full build, run a fast type check:

cargo check

No E0277 means you're good. Then run your tests:

cargo test

Changed main()'s signature? Do a full build to confirm nothing breaks at runtime:

cargo run

Quick reference

  • In main() โ€” change to fn main() -> Result<(), Box<dyn Error>>, add Ok(()) at the end
  • In a function โ€” change return type to Result<T, E> or Option<T>, return the value wrapped in Ok() or Some()
  • In a closure โ€” make the closure return Result, collect into Result<Vec<_>, _>
  • Mixing types โ€” use .ok_or() to convert Option โ†’ Result, or .ok() to convert Result โ†’ Option

E0277 is one of Rust's more helpful errors โ€” it tells you exactly which function needs a different return type and points directly at the offending ?. Once the return types line up, ? is one of the cleanest error propagation patterns in any systems language.

Related Error Notes