Sửa lỗi "the `?` operator can only be used in a function that returns Result or Option" trong Rust

beginner🦀 Rust2026-03-17| Rust 1.13+ (mọi hệ điều hành: Linux, macOS, Windows) — xảy ra khi dùng `?` trong hàm có kiểu trả về không phải `Result` hoặc `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#xử-lý-lỗi#result#option#toán-tử-dấu-chấm-hỏi

Tình huống

Bạn đang refactor code xử lý file I/O, thêm ? vào lệnh File::open() để bỏ qua .unwrap() dài dòng, và Rust ngay lập tức báo lỗi:

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 `()`

Hàm của bạn trả về () — không có gì. Toán tử ? cần nơi để truyền lỗi đi. Không có Result trong phạm vi nghĩa là không có chỗ nào để lỗi được truyền tới.

Tại sao lỗi này xảy ra

Toán tử ? là cú pháp rút gọn cho việc return sớm khi gặp lỗi. Viết some_fn()? tương đương với:

match some_fn() {
    Ok(val) => val,
    Err(e) => return Err(e.into()),  // trả về sớm
}

Lệnh return Err(e) đó yêu cầu hàm bao ngoài phải trả về Result. Nếu không, sẽ không có đường return hợp lệ. Cùng logic áp dụng cho Option: ? trên một Option sẽ return None sớm, nên hàm cũng phải trả về Option.

Cách sửa 1: Thay đổi main() để trả về Result

main() là nơi lỗi này thường gặp nhất. Thêm kiểu trả về và Ok(()) ở cuối:

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> là kiểu bắt-tất-cả — chấp nhận bất kỳ lỗi nào implement trait Error. Đủ dùng cho hầu hết các binary và script nhỏ.

Nếu dùng crate anyhow, code còn gọn hơn:

use anyhow::Result;

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

Cách sửa 2: Thay đổi kiểu trả về của hàm thông thường

Bất kỳ hàm nào cũng có thể dùng ? — chỉ cần kiểu trả về đúng. Đây là trước và sau:

// Trước — lỗi compiler
fn load_config() {
    let contents = std::fs::read_to_string("config.toml")?;
    // ...
}

// Sau — biên dịch thành công
fn load_config() -> Result<String, std::io::Error> {
    let contents = std::fs::read_to_string("config.toml")?;
    Ok(contents)
}

Xử lý nhiều kiểu lỗi trong một hàm? Dùng boxed error giúp bạn tránh phải viết các conversion thủ công giữa chúng:

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)?;  // kiểu lỗi khác, không vấn đề gì
    Ok(config)
}

Cách sửa 3: Dùng ? bên trong closure

Closure phức tạp hơn. Đoạn code này sẽ không biên dịch được:

let results: Vec<_> = paths.iter().map(|p| {
    let content = std::fs::read_to_string(p)?;  // lỗi ở đây
    content
}).collect();

Kiểu trả về được suy luận của closure là String, không phải Result<String, _>. Hãy làm cho closure trả về Result một cách tường minh, sau đó collect vào Result<Vec> — cách này dừng lại ngay khi gặp lỗi đầu tiên:

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

match results {
    Ok(contents) => { /* đọc tất cả file thành công */ }
    Err(e) => eprintln!("Lỗi: {}", e),
}

Muốn bỏ qua lỗi một cách im lặng? Dùng .ok() với filter_map:

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

Cách sửa 4: Kết hợp Result và Option

Kết hợp ResultOption trong một hàm là bẫy thường gặp. Chúng là hai kiểu khác nhau — bạn không thể dùng ? trên cả hai mà không có conversion:

use std::collections::HashMap;

// Lỗi — hàm trả về Result nhưng ? được dùng trên Option
fn get_value(map: &HashMap<String, String>, key: &str) -> Result<String, String> {
    let val = map.get(key)?;  // Option, không phải Result!
    Ok(val.clone())
}

Chuyển Option thành Result bằng .ok_or():

fn get_value(map: &HashMap<String, String>, key: &str) -> Result<String, String> {
    let val = map.get(key).ok_or_else(|| format!("không tìm thấy key '{}'", key))?;
    Ok(val.clone())
}

Chiều ngược lại — hàm trả về Option, nhưng bạn có một Result — dùng .ok() để bỏ qua lỗi:

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

Kiểm tra sau khi sửa

Trước khi build toàn bộ, chạy kiểm tra kiểu nhanh:

cargo check

Không còn E0277 nghĩa là bạn đã xong. Sau đó chạy test:

cargo test

Đã thay đổi signature của main()? Build toàn bộ để xác nhận không có gì bị lỗi runtime:

cargo run

Tóm tắt nhanh

  • Trong main() — đổi thành fn main() -> Result<(), Box<dyn Error>>, thêm Ok(()) ở cuối
  • Trong hàm — đổi kiểu trả về thành Result<T, E> hoặc Option<T>, return giá trị được wrap trong Ok() hoặc Some()
  • Trong closure — làm cho closure trả về Result, collect vào Result<Vec<_>, _>
  • Kết hợp kiểu — dùng .ok_or() để chuyển Option → Result, hoặc .ok() để chuyển Result → Option

E0277 là một trong những lỗi hữu ích nhất của Rust — nó cho bạn biết chính xác hàm nào cần kiểu trả về khác và trỏ trực tiếp vào ? gây ra vấn đề. Khi các kiểu trả về đã khớp, ? là một trong những pattern truyền lỗi gọn gàng nhất trong các ngôn ngữ lập trình hệ thống.

Related Error Notes