Rustの「`?`演算子はResultまたはOptionを返す関数でのみ使用できます」エラーの修正方法

beginner🦀 Rust2026-03-17| Rust 1.13以降(OS問わず:Linux、macOS、Windows)— 戻り値が`Result`または`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

状況の説明

ファイルI/Oのコードをリファクタリング中に、冗長な.unwrap()を省こうとFile::open()の呼び出しに?を付けた途端、Rustが即座にエラーを返します:

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

この関数の戻り値は()(つまり何も返しません)。?演算子はエラーを伝播させる先が必要ですが、Resultがないため伝播先が存在しません。

なぜこのエラーが起きるのか

?演算子はエラー発生時に早期リターンを行うシンタックスシュガーです。some_fn()?は内部的に次のように展開されます:

match some_fn() {
    Ok(val) => val,
    Err(e) => return Err(e.into()),  // 早期リターン
}

このreturn Err(e)は、関数がResultを返すことを前提としています。Resultを返さない関数では有効なリターンパスが存在しません。同じ理屈がOptionにも当てはまります:Optionに対して?を使うとNoneを早期リターンするため、関数もOptionを返す必要があります。

修正方法1:main()の戻り値をResultに変更する

このエラーが最も頻繁に発生するのはmain()です。戻り値の型を追加し、末尾に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>は万能な型で、Errorトレイトを実装するあらゆるエラーを受け付けます。ほとんどのバイナリや使い捨てスクリプトには十分です。

anyhowクレートを使うと、さらにシンプルに書けます:

use anyhow::Result;

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

修正方法2:通常の関数の戻り値の型を変更する

どんな関数でも?を使えます。ただし、適切な戻り値の型が必要です。修正前後を比較します:

// 修正前 — コンパイルエラー
fn load_config() {
    let contents = std::fs::read_to_string("config.toml")?;
    // ...
}

// 修正後 — コンパイル成功
fn load_config() -> Result<String, std::io::Error> {
    let contents = std::fs::read_to_string("config.toml")?;
    Ok(contents)
}

一つの関数で複数のエラー型を扱う場合、ボックス化されたエラーを使うと型変換を手書きしなくて済みます:

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)?;  // 異なるエラー型も問題なし
    Ok(config)
}

修正方法3:クロージャ内で?を使う

クロージャの場合はやや複雑です。次のコードはコンパイルできません:

let results: Vec<_> = paths.iter().map(|p| {
    let content = std::fs::read_to_string(p)?;  // ここでエラー
    content
}).collect();

クロージャの推論された戻り値の型はStringであり、Result<String, _>ではありません。クロージャが明示的にResultを返すようにし、Result<Vec>にcollectします。最初のエラー発生時点で処理が止まります:

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

match results {
    Ok(contents) => { /* 全ファイルの読み込み成功 */ }
    Err(e) => eprintln!("失敗: {}", e),
}

エラーを無視してスキップしたい場合は、.ok()filter_mapを組み合わせます:

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

修正方法4:ResultとOptionを混在させる場合

ResultOptionを一つの関数内で混在させると躓きやすいです。この2つは異なる型であり、変換なしに両方に?を使うことはできません:

use std::collections::HashMap;

// エラー — 関数はResultを返すが、?をOptionに対して使用している
fn get_value(map: &HashMap<String, String>, key: &str) -> Result<String, String> {
    let val = map.get(key)?;  // OptionであってResultではない!
    Ok(val.clone())
}

.ok_or()を使ってOptionResultに変換します:

fn get_value(map: &HashMap<String, String>, key: &str) -> Result<String, String> {
    let val = map.get(key).ok_or_else(|| format!("キー'{}'が見つかりません", key))?;
    Ok(val.clone())
}

逆のパターン(関数がOptionを返すがResultがある場合)は、.ok()でエラーを捨てます:

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

修正の確認

フルビルドの前に、まず高速な型チェックを実行します:

cargo check

E0277が表示されなければOKです。次にテストを実行します:

cargo test

main()のシグネチャを変更した場合は、フルビルドで実行時に問題がないか確認しましょう:

cargo run

クイックリファレンス

  • main()内fn main() -> Result<(), Box<dyn Error>>に変更し、末尾にOk(())を追加する
  • 通常の関数内 — 戻り値の型をResult<T, E>またはOption<T>に変更し、値をOk()またはSome()でラップして返す
  • クロージャ内 — クロージャがResultを返すようにし、Result<Vec<_>, _>にcollectする
  • 型の混在.ok_or()Option → Resultに変換、または.ok()Result → Optionに変換する

E0277はRustの中でも親切なエラーの一つです。どの関数の戻り値の型を変更すべきか、問題のある?がどこにあるかを正確に教えてくれます。戻り値の型が揃えば、?はシステムプログラミング言語の中でも最もシンプルなエラー伝播パターンの一つになります。

Related Error Notes