Mobility Technologies Co., Ltd.
Rust Error Handling
2021/04/28 @MoT.rs
Shunsuke Nakamura
AI技術開発部
Mobility Technologies Co., Ltd.
今日やること
2
- Rustのエラー表現

- エラーによるフロー制御

- よくあるエラー処理

- エラー時の早期リターン

- ライブラリを使用したエラー処理のベストプラクティス

Mobility Technologies Co., Ltd.
Rustのエラー表現
3
■ Rust には Java や Python のような例外はなく、Result型でエラーを表現する
■ Result型は Result<{Ok の時の中身の型}, {Errの時の中身の型}> で定義する
■ 実体はただのEnum型
Mobility Technologies Co., Ltd.
問題1
4
Rust playground にかかれているコードのコンパイルエラーを解消してください
Mobility Technologies Co., Ltd.
解説1
5
Okの際には i32 を、Err の際には ZeroDivisionError を返しているため、
ここでは Result<i32, ZeroDivisionError> を返せば良い
Mobility Technologies Co., Ltd.
エラーによるフロー制御
6
Result型に対して、OkとErrで処理を分けたい場合
Result型はenumなので簡単なパターンマッチングで処理を分岐させられる
※ enumのVariantによるフロー制御
Mobility Technologies Co., Ltd.
問題2
7
Rust playground にかかれているコードのコンパイルエラーを解消してください
ヒント
1. Result<T, E>はOk<T>とErr<E>のenum型です
2. enum & パターンマッチによるフロー制御はスライドの前ページを参照してください
Mobility Technologies Co., Ltd.
解説2
8
Okの際は改めて中身を val として受けて表示し、
Errのときは _ で受けてエラーメッセージを出しています(eprintln! 等を使うとなお良い)
_ で受けると「この変数は使用しない」ということを意味します 参考
Mobility Technologies Co., Ltd.
よくあるエラー処理
9
■ 毎回パターンマッチを書くのは面倒。よくある処理はより短いメソッドを用意してくれている
■ unwrap: Okなら中身の値を取り出し、Errなら終了する。頻出
■ 問題点として、Errだと本当にpanic終了してしまうので、そこでコードが落ちる。
■ 本当に落として良いところでしか使ってはいけない
■ expect: Okなら中身の値を取り出し、Errならメッセージを付けて終了する
■ 問題点として、Errだと本当にpanic終了してしまうので、そこでコードが落ちる。
■ 本当に落として良いところでしか使ってはいけない
■ and_then: Okなら後段の処理を継続し、ErrならErrを伝播させていく
■ 例外処理を上段に任せて正常ケースだけ処理を進めていく時に便利
Mobility Technologies Co., Ltd.
問題3
10
Rust playground にかかれているコードのコンパイルエラーを解消してください
ヒント
Result型に用意されているメソッド一覧です
https://doc.rust-lang.org/std/result/enum.Result.html
Mobility Technologies Co., Ltd.
解説3
11
答え
unwrap, expect, and_then をそれぞれで使います
and_thenの中では次に実行するfunctionを渡してあげる必要があるので、
closure形式で渡しています
Mobility Technologies Co., Ltd.
早期リターンと?演算子
12
メソッドの中で使用している値がOkなら値を取り出して、Errなら早期returnする、というコードをよく書くこと
がある
下のようにパターンマッチを書いてもいいが、毎回これを書くのは面倒なので、
syntax sugarとして、?演算子が用意されている
Mobility Technologies Co., Ltd.
問題4
13
Rust playground にかかれているコードのコンパイルエラーを解消してください
Mobility Technologies Co., Ltd.
解説4
14
? を使って Err 時に早期 returnさせます。
もちろん match を使って早期 return させてもいいですが、? のほうが短く済むし、見やすいです
Mobility Technologies Co., Ltd.
複数のErrorを扱う
15
文字列 a, b を受け取って、i32型としてパースした後に割り算を行う関数を書いたとする
これは以下のコンパイルエラーとなる。
Mobility Technologies Co., Ltd.
より正確な?演算子
16
?演算子は受け取ったErr<E1>と、returnすべきErr<E2>が異なる時(E1 != E2のとき)、
型通りにreturnできるよう、E1をE2に型変換しようとしてくれる(同じ時は同じ型に変換する)
RustでのA → Bへの変換は、Bに対してFrom<A> trait が実装されていれば
- let a: A = From::from(B);
- let a = A::from(B);
のように行うことができる
つまり、?演算子はより正確には↓となる
Mobility Technologies Co., Ltd.
複数のErrorを扱う
17
文字列 a, b を受け取って、i32型としてパースした後に割り算を行う関数を書いたとする
これは以下のコンパイルエラーとなる。
ParseIntError -> &str への変換が実装されていない!
Mobility Technologies Co., Ltd.
問題5
18
Rust playground From traitを実装してください
Mobility Technologies Co., Ltd.
解説5
19
答え
From traitの実装はこのドキュメントに詳しいです。
impl From<変換元> for 変換先 … という実装をしておくと、
変換先::from(変換元) ができるようになります
余談ですが、この実装をすると自動で Into<変換先> for 変換元 が実装されるので、
let a: 変換先 = 変換元.into() が実行できるようになります
Mobility Technologies Co., Ltd.
問題6
20
Rust playground From traitを実装して早期returnしてください
Mobility Technologies Co., Ltd.
解説6
21
答え
先程と同様にFrom traitを実装します
これのおかげで、早期returnの?演算子がFrom::from変換を呼び出すことができます
Mobility Technologies Co., Ltd.
複数のErrorを扱う
22
一つの関数から複数のエラー(Err<E1>, Err<E2>, ...)が返る可能性がある、ということはよくある
対策としては、
- MyErrorのようなenumを定義し、全てのE1, E1→ MyError への変換(From)を実装する
- 自分で定義したエラー型に、毎回変換を実装ていくのは面倒
- Result<T, Box<dyn Error>> を返す関数である、と定義する
- Box<dyn Error> は Error traitを実装した任意の型、を意味する
- 自分で定義したエラー型に、毎回Error traitを実装していくのは面倒
→ 最近の主流: Error周りをよしなにやってくれる便利ライブラリを使う
- anyhow: アプリケーション用と言われている
- thiserror: ライブラリ用と言われている
Mobility Technologies Co., Ltd.
anyhow
23
アプリケーション用Errorライブラリ
- anyhow::Errorの提供
- Box<dyn Error> のようなものになっており、
std::error::Errorを実装しているものなら何でも渡せる
- Err(anyhow::anyhow!(“”)) だけで anyhow::Errorを生成してくれる
- anyhow::Resultを提供
- anyhow::Result<T> だけで Result<T, anyhow::Error> と同じ意味になる
- anyhow::Contextの提供
- 既存のErrorにメッセージを足すことができる
Mobility Technologies Co., Ltd.
thiserror
24
ライブラリ用Errorライブラリ
- thiserror::Errorの提供
- 独自で定義したstructやenumに対して
thiserror::Errorをderiveさせるだけで、
std::error::Errorを実装させることができる
- anyhowと組み合わせることで
かなりコード量を削ることができる
Mobility Technologies Co., Ltd.
問題7
25
Rust playground anyhow / thiserrorを使ってコードを修正してください
Mobility Technologies Co., Ltd.
解説8
26
まずは、std::error::Errorを実装した任意の型を返せるように変更する
途中経過1
しかしこれは残念ながらコンパイルエラーになる。理由は自分で定義したMyErrorがstd::error::Errorを実
装していないから(このstructはDebugしか実装していない)
自分でstd::error::Errorを実装するのは面倒なので、thiserrorを呼び出し、deriveさせるだけで実装したこ
とにしてしまう
途中経過2
最後に、Result<i32, Box<dyn std::error::Error>> がやや長ったらしいので、これを略記できる
anyhowを使って完成
答え
Mobility Technologies Co., Ltd.
参考ページ
27
■ Rust公式docs Error Handling
■ Rust by example Error Handling
■ Rustlings Error Handling
■ thiserror / anyhow の導入の流れ: Rust エラー処理2020

Rust Error Handling

  • 1.
    Mobility Technologies Co.,Ltd. Rust Error Handling 2021/04/28 @MoT.rs Shunsuke Nakamura AI技術開発部
  • 2.
    Mobility Technologies Co.,Ltd. 今日やること 2 - Rustのエラー表現
 - エラーによるフロー制御
 - よくあるエラー処理
 - エラー時の早期リターン
 - ライブラリを使用したエラー処理のベストプラクティス

  • 3.
    Mobility Technologies Co.,Ltd. Rustのエラー表現 3 ■ Rust には Java や Python のような例外はなく、Result型でエラーを表現する ■ Result型は Result<{Ok の時の中身の型}, {Errの時の中身の型}> で定義する ■ 実体はただのEnum型
  • 4.
    Mobility Technologies Co.,Ltd. 問題1 4 Rust playground にかかれているコードのコンパイルエラーを解消してください
  • 5.
    Mobility Technologies Co.,Ltd. 解説1 5 Okの際には i32 を、Err の際には ZeroDivisionError を返しているため、 ここでは Result<i32, ZeroDivisionError> を返せば良い
  • 6.
    Mobility Technologies Co.,Ltd. エラーによるフロー制御 6 Result型に対して、OkとErrで処理を分けたい場合 Result型はenumなので簡単なパターンマッチングで処理を分岐させられる ※ enumのVariantによるフロー制御
  • 7.
    Mobility Technologies Co.,Ltd. 問題2 7 Rust playground にかかれているコードのコンパイルエラーを解消してください ヒント 1. Result<T, E>はOk<T>とErr<E>のenum型です 2. enum & パターンマッチによるフロー制御はスライドの前ページを参照してください
  • 8.
    Mobility Technologies Co.,Ltd. 解説2 8 Okの際は改めて中身を val として受けて表示し、 Errのときは _ で受けてエラーメッセージを出しています(eprintln! 等を使うとなお良い) _ で受けると「この変数は使用しない」ということを意味します 参考
  • 9.
    Mobility Technologies Co.,Ltd. よくあるエラー処理 9 ■ 毎回パターンマッチを書くのは面倒。よくある処理はより短いメソッドを用意してくれている ■ unwrap: Okなら中身の値を取り出し、Errなら終了する。頻出 ■ 問題点として、Errだと本当にpanic終了してしまうので、そこでコードが落ちる。 ■ 本当に落として良いところでしか使ってはいけない ■ expect: Okなら中身の値を取り出し、Errならメッセージを付けて終了する ■ 問題点として、Errだと本当にpanic終了してしまうので、そこでコードが落ちる。 ■ 本当に落として良いところでしか使ってはいけない ■ and_then: Okなら後段の処理を継続し、ErrならErrを伝播させていく ■ 例外処理を上段に任せて正常ケースだけ処理を進めていく時に便利
  • 10.
    Mobility Technologies Co.,Ltd. 問題3 10 Rust playground にかかれているコードのコンパイルエラーを解消してください ヒント Result型に用意されているメソッド一覧です https://doc.rust-lang.org/std/result/enum.Result.html
  • 11.
    Mobility Technologies Co.,Ltd. 解説3 11 答え unwrap, expect, and_then をそれぞれで使います and_thenの中では次に実行するfunctionを渡してあげる必要があるので、 closure形式で渡しています
  • 12.
    Mobility Technologies Co.,Ltd. 早期リターンと?演算子 12 メソッドの中で使用している値がOkなら値を取り出して、Errなら早期returnする、というコードをよく書くこと がある 下のようにパターンマッチを書いてもいいが、毎回これを書くのは面倒なので、 syntax sugarとして、?演算子が用意されている
  • 13.
    Mobility Technologies Co.,Ltd. 問題4 13 Rust playground にかかれているコードのコンパイルエラーを解消してください
  • 14.
    Mobility Technologies Co.,Ltd. 解説4 14 ? を使って Err 時に早期 returnさせます。 もちろん match を使って早期 return させてもいいですが、? のほうが短く済むし、見やすいです
  • 15.
    Mobility Technologies Co.,Ltd. 複数のErrorを扱う 15 文字列 a, b を受け取って、i32型としてパースした後に割り算を行う関数を書いたとする これは以下のコンパイルエラーとなる。
  • 16.
    Mobility Technologies Co.,Ltd. より正確な?演算子 16 ?演算子は受け取ったErr<E1>と、returnすべきErr<E2>が異なる時(E1 != E2のとき)、 型通りにreturnできるよう、E1をE2に型変換しようとしてくれる(同じ時は同じ型に変換する) RustでのA → Bへの変換は、Bに対してFrom<A> trait が実装されていれば - let a: A = From::from(B); - let a = A::from(B); のように行うことができる つまり、?演算子はより正確には↓となる
  • 17.
    Mobility Technologies Co.,Ltd. 複数のErrorを扱う 17 文字列 a, b を受け取って、i32型としてパースした後に割り算を行う関数を書いたとする これは以下のコンパイルエラーとなる。 ParseIntError -> &str への変換が実装されていない!
  • 18.
    Mobility Technologies Co.,Ltd. 問題5 18 Rust playground From traitを実装してください
  • 19.
    Mobility Technologies Co.,Ltd. 解説5 19 答え From traitの実装はこのドキュメントに詳しいです。 impl From<変換元> for 変換先 … という実装をしておくと、 変換先::from(変換元) ができるようになります 余談ですが、この実装をすると自動で Into<変換先> for 変換元 が実装されるので、 let a: 変換先 = 変換元.into() が実行できるようになります
  • 20.
    Mobility Technologies Co.,Ltd. 問題6 20 Rust playground From traitを実装して早期returnしてください
  • 21.
    Mobility Technologies Co.,Ltd. 解説6 21 答え 先程と同様にFrom traitを実装します これのおかげで、早期returnの?演算子がFrom::from変換を呼び出すことができます
  • 22.
    Mobility Technologies Co.,Ltd. 複数のErrorを扱う 22 一つの関数から複数のエラー(Err<E1>, Err<E2>, ...)が返る可能性がある、ということはよくある 対策としては、 - MyErrorのようなenumを定義し、全てのE1, E1→ MyError への変換(From)を実装する - 自分で定義したエラー型に、毎回変換を実装ていくのは面倒 - Result<T, Box<dyn Error>> を返す関数である、と定義する - Box<dyn Error> は Error traitを実装した任意の型、を意味する - 自分で定義したエラー型に、毎回Error traitを実装していくのは面倒 → 最近の主流: Error周りをよしなにやってくれる便利ライブラリを使う - anyhow: アプリケーション用と言われている - thiserror: ライブラリ用と言われている
  • 23.
    Mobility Technologies Co.,Ltd. anyhow 23 アプリケーション用Errorライブラリ - anyhow::Errorの提供 - Box<dyn Error> のようなものになっており、 std::error::Errorを実装しているものなら何でも渡せる - Err(anyhow::anyhow!(“”)) だけで anyhow::Errorを生成してくれる - anyhow::Resultを提供 - anyhow::Result<T> だけで Result<T, anyhow::Error> と同じ意味になる - anyhow::Contextの提供 - 既存のErrorにメッセージを足すことができる
  • 24.
    Mobility Technologies Co.,Ltd. thiserror 24 ライブラリ用Errorライブラリ - thiserror::Errorの提供 - 独自で定義したstructやenumに対して thiserror::Errorをderiveさせるだけで、 std::error::Errorを実装させることができる - anyhowと組み合わせることで かなりコード量を削ることができる
  • 25.
    Mobility Technologies Co.,Ltd. 問題7 25 Rust playground anyhow / thiserrorを使ってコードを修正してください
  • 26.
    Mobility Technologies Co.,Ltd. 解説8 26 まずは、std::error::Errorを実装した任意の型を返せるように変更する 途中経過1 しかしこれは残念ながらコンパイルエラーになる。理由は自分で定義したMyErrorがstd::error::Errorを実 装していないから(このstructはDebugしか実装していない) 自分でstd::error::Errorを実装するのは面倒なので、thiserrorを呼び出し、deriveさせるだけで実装したこ とにしてしまう 途中経過2 最後に、Result<i32, Box<dyn std::error::Error>> がやや長ったらしいので、これを略記できる anyhowを使って完成 答え
  • 27.
    Mobility Technologies Co.,Ltd. 参考ページ 27 ■ Rust公式docs Error Handling ■ Rust by example Error Handling ■ Rustlings Error Handling ■ thiserror / anyhow の導入の流れ: Rust エラー処理2020