RustのResult
Option が「ないかもしれない値」を表すのに対して、Result は「失敗するかもしれない処理」を表します。そして、値がないだけの場合とは違い、失敗にはたいてい理由がついてきます。
Resultの形
Section titled “Resultの形”Result も標準ライブラリにある列挙型(enum)です。今回は、2つのバリアントがどちらもデータを持っています。
enum Result<T, E> { Ok(T), // 成功。値を持つ Err(E), // 失敗。エラーを持つ}失敗するかもしれない関数
Section titled “失敗するかもしれない関数”型変換のページで出てきた .parse() は、ちょうどいい実例です。テキストを数値に変換する処理は、そのテキストが数値でなければ失敗します。
fn main() { let good: Result<i32, std::num::ParseIntError> = "42".parse(); let bad: Result<i32, std::num::ParseIntError> = "abc".parse();
println!("{:?}", good); println!("{:?}", bad);}実行結果:
Ok(42)Err(ParseIntError { kind: InvalidDigit })InvalidDigit は「数字として正しくない文字が含まれている」という意味で、Rust自身が出すメッセージなので英語のままです。
matchでResultを扱う
Section titled “matchでResultを扱う”fn main() { let input = "42";
match input.parse::<i32>() { Ok(number) => println!("変換できました: {number}"), Err(e) => println!("変換に失敗しました: {e}"), }}Option を match で扱うときと同じ形です。ただし Err のアームでは、「何もなかった」というだけでなく、中身を調べたり報告したりできる実際のエラー値を受け取れます。
Resultを返す関数を自分で書く
Section titled “Resultを返す関数を自分で書く”fn divide(a: f64, b: f64) -> Result<f64, String> { if b == 0.0 { Err(String::from("0で割ることはできません")) } else { Ok(a / b) }}
fn main() { match divide(10.0, 2.0) { Ok(result) => println!("結果: {result}"), Err(e) => println!("エラー: {e}"), }
match divide(10.0, 0.0) { Ok(result) => println!("結果: {result}"), Err(e) => println!("エラー: {e}"), }}実行結果:
結果: 5エラー: 0で割ることはできませんResult<f64, String> を返すことで、呼び出す側は必ず失敗の可能性に向き合うことになります。0による割り算を、うっかり普通の数値として扱ってしまうことはありえません。
失敗するかもしれない処理をいくつもつなげると、match がどんどん深く入れ子になってしまいます。? 演算子を使えば、それを平らにできます。? の意味は「これが Err なら、そのエラーを今の関数からすぐに返す。そうでなければ Ok の中の値をちょうだい」です。
fn parse_and_double(input: &str) -> Result<i32, std::num::ParseIntError> { let number = input.parse::<i32>()?; // 変換に失敗したら、ここで早めにreturnする Ok(number * 2)}
fn main() { println!("{:?}", parse_and_double("21")); println!("{:?}", parse_and_double("oops"));}実行結果:
Ok(42)Err(ParseIntError { kind: InvalidDigit })? がなければ、number を取り出すか、エラーと一緒に早めに抜けるかのためだけに、この行にも match が必要になります。? はそれをたった1文字でやってくれるのです。ただし、? が使えるのは、その関数自体が Result(または Option)を返す場合だけです。そうでないと、エラーを返す先がないからです。
Resultとpanic!
Section titled “Resultとpanic!”Rustには panic! もあります。これはプログラムをすぐに止めるもので、次のページで詳しく説明します。目安としては、呼び出す側が対処したいと思うのが自然な失敗(不正な入力、ファイルが見つからないなど)には Result を使いましょう。panic! は、本当にバグがあることを示していて、処理を続けるよりも止めたほうがましな状況のためにとっておきます。
関数が何通りもの理由で失敗するようになると、ただの String のエラーでは足りなくなってきます。自分でエラー型を定義する方法は独自のエラー型のページで、多くの Rust プロジェクトが決まりきったコードを減らすために使っているクレートはanyhowとthiserrorのページで紹介しています。
1. 年齢をチェックする
Section titled “1. 年齢をチェックする”fn parse_age(input: &str) -> Result<u32, String> を書いてください。テキストが数値でないとき、または数値が150を超えるときは、わかりやすいメッセージ付きの Err を返します。"34"、"abc"、"200" で試してみましょう。ヒント:input.parse::<u32>() を match して、Ok のアームの中で if を使って大きさをチェックします。
解答を見る
fn parse_age(input: &str) -> Result<u32, String> { match input.parse::<u32>() { Ok(age) => { if age > 150 { Err(format!("{age}歳というのは現実的ではありません")) } else { Ok(age) } } Err(_) => Err(format!("'{input}' は数値ではありません")), }}
fn main() { for input in ["34", "abc", "200"] { match parse_age(input) { Ok(age) => println!("年齢: {age}"), Err(e) => println!("エラー: {e}"), } }}実行結果:
年齢: 34エラー: 'abc' は数値ではありませんエラー: 200歳というのは現実的ではありません2. ?を使って2つの数を足す
Section titled “2. ?を使って2つの数を足す”2つの文字列を数値に変換して、その合計を返す fn add_strings(a: &str, b: &str) -> Result<i32, std::num::ParseIntError> を書いてください。match ではなく ? を使います。("10", "20") と ("10", "twenty") の結果を表示しましょう。ヒント:parse 1回につき ? を1つ付けます。最後の行で合計を Ok(...) で包みます。
解答を見る
fn add_strings(a: &str, b: &str) -> Result<i32, std::num::ParseIntError> { let x = a.parse::<i32>()?; let y = b.parse::<i32>()?; Ok(x + y)}
fn main() { println!("{:?}", add_strings("10", "20")); println!("{:?}", add_strings("10", "twenty"));}実行結果:
Ok(30)Err(ParseIntError { kind: InvalidDigit })Err(ParseIntError { kind: InvalidDigit }) は、標準ライブラリのエラーを {:?} で表示したものです。「数字として使えない文字が含まれていた」という意味です。
Result<T, E>は、成功(Ok)するか、エラー値とともに失敗(Err)するかもしれない処理を表します。matchで両方のケースをはっきり扱います。Optionと同じパターンです。?演算子はErrを自動的に早めに返すので、失敗しうる呼び出しのたびにmatchを書く必要がありません。- 呼び出す側が回復できそうなエラーには、
panic!よりもResultを使いましょう。
理解度チェック
Section titled “理解度チェック”理解度チェック
1. Result<T, E> の2つのバリアントはどれですか?
2. Resultを返す関数の中で、? 演算子は何をしますか?
3. このページによると、panic! よりも Result を使うべきなのはどんなときですか?
4. このページの divide の例で、divide(10.0, 0.0) は何を返しますか?
5. ? 演算子が、Result(またはOption)を返す関数の中でしか使えないのはなぜですか?
スコア: 0 / 5
さらに詳しく
Section titled “さらに詳しく”- Resultで回復可能なエラー — The Rust Programming Language 日本語版:
Result、unwrap、expect、?演算子を、ファイルを開く例で説明する公式の章です。 std::result— 標準ライブラリのドキュメント(英語):Resultの扱い方の全体像です。map_err、unwrap_or、okなどのメソッドも載っています。- Result — Rust By Example 日本語版:
Resultを処理する例や、mainからResultを返す例を実行しながら学べます。 - ? — Rust By Example 日本語版:
?が入れ子のmatchをどう置き換えるのかに絞った、短い解説です。