Programmingシステムプログラマー

Result<T, E>を介したエラーハンドリングの仕組みと、コードの安全性と可読性を損なわないようにクエスチョンマークオペレーター(?)を正しく使用する方法を説明してください。

Hintsage AIアシスタントで面接を突破

回答。

Rustは明示的なエラーハンドリングに基づいて構築されています:例外は存在せず、代わりにResult<T, E>という戻り値が使用されます。これにより、コードの安全性と予測可能性が確保されます。

問題の背景:

多くの言語は例外の道を歩んできたため、予期しない状況や実行時に例外を明示的に処理する必要が生じました。Rustは最初から型による制御を重視し、すべてのエラーは関数のシグネチャの一部でなければなりません。

問題点:

主な課題は、エラーが無視されず隠されないようにコードを書くことです。「クラッシュ」する関数はなく、同時にコードがコンパクトで可読性を保つ必要があります。情報を失わず、論理を混乱させずにエラーを上位に伝播させる必要があります。

解決策:

重要なツールはResult<T, E>型と?オペレーターであり、これは結果を自動的に「アンラップ」します:エラーが発生した場合、関数から即座に退出しエラーを返し、成功した場合は値を返します。

コード例:

fn read_number(file: &str) -> Result<i32, std::io::Error> { let content = std::fs::read_to_string(file)?; let num: i32 = content.trim().parse()?; Ok(num) }

主な特徴:

  • シグネチャにすべての潜在的なエラーの明示的な宣言
  • ?オペレーターによってネストを簡略化(if-let/unwrap/expectを除去)
  • エラーは.map_err()や他のメソッドを通じて簡単に「ラップ」または変換可能

トリッキーな質問。

ユニット(void)を返す関数で?を使用できますか?

いいえ、?オペレーターはResultまたはOptionを返す関数内でのみ許可されます。関数が()を返す場合、?を使用することはできません。

複数の呼び出しでエラータイプが異なる場合、どうなりますか?

コンパイルエラーになります:エラータイプは明確に定義されている必要があります。すべてのエラーを.map_err()を使用して統一するか、thiserrorを使用するか、APIレベルでenumラッパーを記述する必要があります。例:

fn foo() -> Result<_, MyError> { let a = bar()?; let b = baz().map_err(MyError::Baz)?; Ok() }

内部ロジックでの.unwrap()の危険性は?

「主要な」コード内でunwrap()を安心して使用できるという誤解が広まっていますが、「絶対にクラッシュしない」という保証はありません。実際には、小さな目に見えないエラーでも実行時パニックを引き起こし、プログラムの安全性が損なわれます。

一般的なエラーとアンチパターン

  • 内部ロジックにおける全エラーをunwrap/expectでキャッチすること
  • map_err(|_| …)におけるコンテキスト喪失、エラーが元の情報を保持せずに閉じられること
  • 各エラーを手動で処理する際に?なしで冗長なネストを生成すること

実例

ネガティブケース

プロダクションコードで迅速なデバッグの後、多くのunwrap()が残されました。その結果、ユーザーの不正な入力によるパースのどんな失敗でもアプリケーションがクラッシュしました。

利点:

  • 迅速なデバッグ

欠点:

  • 潜在的なアプリケーションクラッシュ、原因を特定するのが難しい

ポジティブケース

完全なResult<T, E>スタックを使用し、エラーを明示的に記述し、情報を保持しつつ?.map_err()のみを使用しました。

利点:

  • デバッグが容易で予測可能な動作

欠点:

  • わずかに「雑音の多い」エラータイプが増える