導入
Result を返す処理を何段も重ねると、毎回 match で Err を上に返すコードが冗長になります。これを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>()?…ResultがOkなら中身を取り出して続行、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 の中でも ? が使えるようになります。