Rustのエラーハンドリングにanyhowとthiserrorを使う
上記の歯車本、良い本でお世話になっていますが、エラー処理に関する部分について、今回じっくり読んでみました。そこで色々と考えることもあったので、紹介がてら書いてみます。
Rustのエラーハンドリングは、結構たいへん。何年か前にRustを書き始めた時は、まだイニシエのtry!
が生きている頃でした。「エラーを表すResult型を基本に、panicも一定のルールの下で使う」という大原則は変わっていないと思うんですが、定石は本当に定まってなかったですよね。その頃に読んでた公式ドキュメントを探してみたんですがもう見つからないですね。今はそのページがリニューアルされていて、さらに長い章になっているようです。
書いていて、多くの人が感じるであろう煩雑さを軽減するのが歯車本でも紹介されていた、上記のanyhow
thiserror
の2つのライブラリです。anyhow
が行っているのは、map
やand_then
などのコンビネータの最後にcontext
with_context
関数を使えるようにしたこと。柔軟に戻り値のanyhow::Result
へと変換できるようにしている。それから、早期リターン用のマクロであるbail
とensure
が存在する。
もう一つのthiserror
は、enum宣言の中で使う。エラー種別の実装を一箇所に固めることができるし、from
とかの実装が不要になる。
なんだろう、人類が求めていたもの感が強い。とても便利です。
大抵は、関数の戻り値をResultにしておいてこれらのライブラリを使えば、いい感じにエラーハンドリングできそうですね。
さて、これら2つのライブラリが便利に感じる理由を考えてました。「エラーを型として扱う」というのはいいんですけど、「個々のエラーをそれぞれ型として扱う」ことになると、それらが同一の種類のものであるとどこかで示す必要がでてきます。継承がある言語ならば親のエラー型があればいいんですが、Rustだとそれらはenumで扱うことになる。そのときに煩雑さが発生するんだと思います。
なんだかんだで、「エラー」というでっかいカタマリとして扱いたい、コードを書いてる時は「エラーと原因」に注意がいきがちで、それ以外のことはあまり考えたくない、ということですよね、たぶん。Haskellみたく型クラスがあればいいのかなとも思ったけど、Error
トレイトがそれにあたるのか。グループ化するためのenumとシグニチャを決めるトレイトが両方必要なんですね。ともかくがんがん使って経験値を貯めましょう。