Rustのエラー
Rustには2種類のエラーが存在する。
- 回復可能 : Result<T, E>
- 回復不能なエラー : panic!
Rustには例外が存在しない。他言語ではこれらのエラーを区別することはなく、例外にしていることが多い。
panic
panic!はマクロ。
パニックが発生すると、プログラムはロールバックする。言語がスタックを遡り、 遭遇した各関数のデータをclean upする。
パニックのabort
パニック発生後、ロールバックに対して、即座に異常終了し、clean upせずにプログラムを終了させると、プログラムが使用していたメモリは、 OSがclean upしなければならない。しかし、プロジェクトにおいて、実行可能ファイルを極力小さくする必要があれば、 異常終了するがある。
Cargo.tomlの[profile.*]
に以下を記述すると、パニックになった際の挙動をロールバックから異常終了に変更できる。
そして、main関数を作る。
実行結果は以下のようになる。
また、abortをすると以下のようになる。
バックトレース
環境変数RUST_BACKTRACE
を設定すると、バックトレースを出力することができる。
設定値 | 挙動 |
---|
0 | バックトレースを非表示 |
1 | バックトレースを表示 |
full | バックトレースのverboseを表示 |
Result
Resultは、回復可能なエラーである。
Result型とは?
Result型とは、標準ライブラリのstd::result
モジュールで定義されている
例えば、引数が1のときにエラーを返す。
match
呼び出し側では、matchを使ってエラー処理を行う。
特定のエラーでパニック
例えば先ほどの例で1を入力させた時のエラーにパニックを起こしたい場合は、
そして、呼び出し側で、Err(ref error) if error == "1はダメ"
のように特定のエラーに対してハンドリングすることが可能である。
unwrap
match
だと冗長になるときがある。unwrap()を使うと、エラーの時panic、正常時は値を返す。
これを実行すると、以下のようになる。
expect
expectはunwrapと似ているが、panicのエラーメッセージを自分でカスタマイズできる。
エラーの委譲
エラーはgoなどと同じように、呼び出し元にエラーを返す。
?演算子
?
でearly returnをシンプルに書くことができる。
main関数でも以下のようにResultを返すことで?
を使うことができるが、Resultを返さなければ、match
かResultのヘルパー関数を使用するしかない。
panicかResultか
https://doc.rust-jp.rs/book-ja/ch09-03-to-panic-or-not-to-panic.html
Optionとunwrap
Some(T)
は存在するとき。
以下はmatchで明示的にNoneを処理している。
ちなみに、ResultはリッチなOption型であり、値の不在の可能性の代わりにエラーの可能性を示す。
unwrapを使って暗黙的に処理することも可能。
Noneのときpanicになる。
?によるハンドリング
Combinators: map
Option<T>に対するmatchの冗長な書き方を、Combinatorsのmapを使ってシンプルに書ける。
このPeel()はCombinatorsを使うと以下のようになる。
また、複数のmap()
をチェインさせることもできる。
Rust Playground
Combinators: and_then
以下はmatchで書いた場合の例。
これのcookableはCombinatorsのmapを使うと以下のようになる。
さらにCombinatorsのand_thenを使用するとより簡潔にかける。
Rust Playground
or()
or、or_else、get_or_insert、'get_or_insert_with`
Rustでは*_or()
は先行評価である。
or()
は、先行評価・連鎖可能・空の値を保持する
or_else()
Rustでは*_or_else()
は遅延評価である。
or_else()
は、遅延評価・連鎖可能・空の値を保持する
get_or_insert()()
get_or_insert()
は、先行評価・空の値は変更される
get_or_insert_with()
get_or_insert_with()
は、遅延評価・空の値は変更される
Resutlのエリアス
特定のResult型に対してエイリアスをはれる。stdライブラリはio::Resultを提供する。
try!マクロ
?
ができる前、同様の動作をtry!マクロで行っていた。現在は?
を使うことが推奨されている。
複数のエラー
OptionがResultと連携したり、Result<T, Error1>がResult<T, Error2>と連携すると複雑になる。
OptionからResultを取り出す
単にお互いを埋め込んでしまう方法。
Optionの中身がNoneの場合はそのまま処理を進め、エラーの検出に限り実行を止める。(?を使った時のように)。いくつかのコンビネータによって簡単にResultとOptionをスワップできる。
エラー型の定義
異なるエラー型をマスクし、単一のエラー型として扱えるようにすると、コードがシンプルになる場合がある。
「良い」エラー型
- 異なるエラーをまとめて同じ型として扱う
- ユーザーに優しいエラーメッセージを提供する
- 他の型との比較を楽にする
- エラーについての情報を保持できる
- 他のエラーと問題なく連携できる
以下は独自のエラー型を作成した例である。
エラーのBox
元のエラーを維持しながらシンプルなコードを書くには、Boxする。元のエラー型はランタイムまで判明せず、静的に決定されないことがデメリット
標準ライブラリはBoxに、Fromを介してあらゆるErrorトレイトを実装した型からBox<Error>トレイトオブジェクトへの変換を実装させることで、エラーをboxしやすくしてくれる。
?の他の活用法
エラーのラップ
Boxする方法の代替として、エラーを自前のエラー型としてラップする方法もある。
これはエラーの処理のボイラープレートを増やしてしまい、全てのアプリケーションで必要になる訳では無い。これらのボイラープレートの処理を代わりにやってくれるようなライブラリもある。
そのほかの手段
Resultのイテレート
Resultをイテレートする - Rust By Example 日本語版
thiserror クレート
thiserrorでカスタムエラーを実装する
anyhow クレート
anyhowでRustのエラーハンドリングメモ
参考文献
- Introduction - Rust By Example 日本語版
- Rustにおけるエラーハンドリング