本文へスキップ
BecomeCoder

Rustコース · 第8章 コレクションとエラー処理 · レッスン35

? 演算子 ― エラーの伝播を簡潔に

ブラウザで完結

導入

Result を返す処理を何段も重ねると、毎回 matchErr を上に返すコードが冗長になります。これを1文字で書けるのが ? 演算子です。Rust のエラー処理を実用的にしている立役者です。

説明

文字列を数値に変換して2倍する」処理を、match で書くとこうです。

fn double_verbose(text: &str) -> Result<i32, std::num::ParseIntError> {
    let n = match text.parse::<i32>() {
        Ok(v) => v,
        Err(e) => return Err(e),    // 失敗ならそのまま上に返す
    };
    Ok(n * 2)
}

? を使うと、同じことがぐっと短くなります。

fn double(text: &str) -> Result<i32, std::num::ParseIntError> {
    let n = text.parse::<i32>()?;   // 成功なら中身を取り出す。失敗なら即 return Err
    Ok(n * 2)
}

fn main() {
    println!("{:?}", double("21"));    // Ok(42)
    println!("{:?}", double("abc"));   // Err(...)
}
  • text.parse::<i32>()?ResultOk なら中身を取り出して続行Err ならその場で関数から Err を返す(自動的に early return)。
  • ? を使う関数自身が Result(や Option)を返す型でなければならない、という制約があります(Err を返すため)。
  • 複数の失敗しうる処理を a()?; b()?; c()?; と連ねられ、「どこかで失敗したら全体が失敗を返す」が自然に書けます。

? のおかげで、Rust のエラー処理は「例外のような簡潔さ」と「値としての明示性・追跡可能性」を両立します。成功の本流のロジックが ? の連なりで素直に読め、失敗の伝播はコンパイラが面倒をみてくれます。

補足:?Option に対しても使えます(None なら早期に None を返す)。また実務では、種類の違うエラーをまとめる Box<dyn Error>anyhow クレートと組み合わせて使うことが多いですが、まずは「? は Err を上に投げるショートカット」と押さえれば十分です。

試すには

match 版と ? 版を見比べて、? が「Ok なら中身、Err なら早期リターン」を1文字で表していることを味わってください。main 関数自体を fn main() -> Result<(), Box<dyn std::error::Error>> にすると、main の中でも ? が使えるようになります。

実際に動かしてみよう

下のエディタにRustを書いて「実行」を押すと、学習用シミュレータが println! の出力を表示します(本物のrustcではなく、教材の範囲を再現した軽量エンジンです)。struct・enum・match・トレイト・Vec/String/HashMap・Option/Result なども動きます。本文の例を書き換えて動かしてみましょう(所有権・借用チェッカーのコンパイルエラーは再現しないため、動きの確認用として使ってください)。

Rust — ブラウザ内で実行(学習用シミュレータ)

Rustの教材サブセットを動かす学習用シミュレータを読み込みます(本物のrustcではなく、動きを再現した軽量な自作エンジンです)。
スクロールして表示された時点でも自動で読み込まれます。