↓ メインコンテンツへスキップ

Rust のエラーハンドリング

分類
#

  • 大きく分けて2つ
    • 回復不可能なエラー: panic! マクロを用いる
      • 配列の境界を超えた箇所にアクセスしようとすることなど
      • プログラムを強制的に終わらせる
    • 回復可能なエラー: Result<T, E> を用いる
      • ファイルが見つからないなどの回復可能なエラーには、問題をユーザに報告し、処理を再試行する

panic! で回復不可能なエラーを起こす
#

  • panic! マクロが実行されると、プログラムは失敗のメッセージを表示し、終了する。

    • 標準では、プログラムはスタックの巻き戻しを行う。
      • スタックを遡り、 遭遇した各関数のデータを片付ける。
    • しかし、これではやることが多くなる場合があるので、オプションとして、データの片付けをせずにプログラムを異常終了させる方法も用意されている。
      • 結果として、プロジェクトの実行可能ファイルは小さくなる。
      • ただし、この場合、スタックに残ったデータの処理は OS で行う必要がある。
      • このオプションを選択する場合は、 Cargo.toml の [profile] 欄に panic = 'abort' を追記する
        • 下記の例は、リリースモードにて異常終了を選択する場合の記述

          1[profile.release]
          2panic = 'abort'
  • これが起こる場合の大半は、 何らかのバグが検出された時であり、プログラマ対して対処方法が明確ではない場合。

  • 実際に自分で起こす場合は以下のようにする:

    1fn main() {
    2    panic!("crash and burn");  //クラッシュして炎上
    3}
    • これを実行すると、以下のようなエラーメッセージが発生:

      1$ cargo run
      2   Compiling panic v0.1.0 (file:///projects/panic)
      3    Finished dev [unoptimized + debuginfo] target(s) in 0.25 secs
      4     Running `target/debug/panic`
      5thread 'main' panicked at 'crash and burn', src/main.rs:2:4
      6('main'スレッドはsrc/main.rs:2:4の「クラッシュして炎上」でパニックしました)
      7note: Run with `RUST_BACKTRACE=1` for a backtrace.
  • ライブラリで実装されている panic! を起こしたのが以下の例:ベクタの境界を超えた要素へのアクセスを試みた

    1fn main() {
    2    let v = vec![1, 2, 3];
    3
    4    v[99];
    5}
    • これを実行すると以下のメッセージが出る:

      1$ cargo run
      2   Compiling panic v0.1.0 (file:///projects/panic)
      3    Finished dev [unoptimized + debuginfo] target(s) in 0.27 secs
      4     Running `target/debug/panic`
      5thread 'main' panicked at 'index out of bounds: the len is 3 but the index is
      699', /checkout/src/liballoc/vec.rs:1555:10
      7('main'スレッドは、/checkout/src/liballoc/vec.rs:1555:10の
      8「境界外番号: 長さは3なのに、添え字は99です」でパニックしました)
      9note: Run with `RUST_BACKTRACE=1` for a backtrace.
      • このエラーは、自分のファイルではない vec.rs (標準ライブラリの Vec<T> の実装)ファイルを指している。
        • ベクタ v に対して [] を使った時に走るコードは、 vec.rs に存在し、ここで実際に panic! が発生している。
    • エラー発生に至るまでに呼び出された全関数を見るバックトレースを得るには、環境変数 RUST_BACKTRACE を 0 以外に設定する

      • 実際に吐き出されるバックトレースは以下のような感じ
       1$ RUST_BACKTRACE=1 cargo run
       2    Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
       3     Running `target/debug/panic`
       4thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', /checkout/src/liballoc/vec.rs:1555:10
       5stack backtrace:
       6   0: std::sys::imp::backtrace::tracing::imp::unwind_backtrace
       7             at /checkout/src/libstd/sys/unix/backtrace/tracing/gcc_s.rs:49
       8   1: std::sys_common::backtrace::_print
       9             at /checkout/src/libstd/sys_common/backtrace.rs:71
      10   2: std::panicking::default_hook::{{closure}}
      11             at /checkout/src/libstd/sys_common/backtrace.rs:60
      12             at /checkout/src/libstd/panicking.rs:381
      13   3: std::panicking::default_hook
      14             at /checkout/src/libstd/panicking.rs:397
      15   4: std::panicking::rust_panic_with_hook
      16             at /checkout/src/libstd/panicking.rs:611
      17   5: std::panicking::begin_panic
      18             at /checkout/src/libstd/panicking.rs:572
      19   6: std::panicking::begin_panic_fmt
      20             at /checkout/src/libstd/panicking.rs:522
      21   7: rust_begin_unwind
      22             at /checkout/src/libstd/panicking.rs:498
      23   8: core::panicking::panic_fmt
      24             at /checkout/src/libcore/panicking.rs:71
      25   9: core::panicking::panic_bounds_check
      26             at /checkout/src/libcore/panicking.rs:58
      27  10: <alloc::vec::Vec<T> as core::ops::index::Index<usize>>::index
      28             at /checkout/src/liballoc/vec.rs:1555
      29  11: panic::main
      30             at src/main.rs:4
      31  12: __rust_maybe_catch_panic
      32             at /checkout/src/libpanic_unwind/lib.rs:99
      33  13: std::rt::lang_start
      34             at /checkout/src/libstd/panicking.rs:459
      35             at /checkout/src/libstd/panic.rs:361
      36             at /checkout/src/libstd/rt.rs:61
      37  14: main
      38  15: __libc_start_main
      39  16: <unknown> 

Result で回復可能なエラーを起こす
#

  • Result enum は Ok と Err の2つから成る

    1enum Result<T, E> {
    2    Ok(T),
    3    Err(E),
    4}
    • T と E はジェネリクス
  • 使い方は以下のような感じ

     1use std::fs::File;
     2
     3fn main() {
     4    // let f: u32 = File::open("hello.txt"); と書くとコンパイルエラー
     5		// 帰ってくる型は std::result::Result
     6    let f = File::open("hello.txt");
     7
     8    let f = match f {
     9        Ok(file) => file,
    10        Err(error) => {
    11            // ファイルを開く際に問題がありました
    12            panic!("There was a problem opening the file: {:?}", error)
    13        },
    14    };
    15}

色々な種類のエラーを異なる方法で扱う
#

 1use std::fs::File;
 2use std::io::ErrorKind;
 3
 4fn main() {
 5    let f = File::open("hello.txt");
 6
 7    let f = match f {
 8        Ok(file) => file,
 9        Err(ref error) if error.kind() == ErrorKind::NotFound => {
10            match File::create("hello.txt") {
11                Ok(fc) => fc,
12                Err(e) => {
13                    panic!(
14                        //ファイルを作成しようとしましたが、問題がありました
15                        "Tried to create file but there was a problem: {:?}",
16                        e
17                    )
18                },
19            }
20        },
21        Err(error) => {
22            panic!(
23                "There was a problem opening the file: {:?}",
24                error
25            )
26        },
27    };
28}
  • if error.kind() == ErrorKind::Notfound という条件式は、マッチガードと呼ばれる
    • アームのパターンをさらに洗練する match アーム上のおまけの条件式

エラー時にパニックするショートカット
#

  • unwrap : Result が Ok の場合は中身の型を返す、 Err の場合は panic! を起こす

    1use std::fs::File;
    2
    3fn main() {
    4    let f = File::open("hello.txt").unwrap();
    5}
    • ファイルが見つからない場合は以下のようなメッセージが出力される

      1thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error {
      2repr: Os { code: 2, message: "No such file or directory" } }',
      3src/libcore/result.rs:906:4
      4('main'スレッドは、src/libcore/result.rs:906:4の
      5「`Err`値に対して`Result::unwrap()`が呼び出されました: Error{
      6repr: Os { code: 2, message: "そのようなファイルまたはディレクトリはありません" } }」でパニックしました)
  • expect : unwrap と基本的に同じだが、 panic! のメッセージ内容も記述可能

    1use std::fs::File;
    2
    3fn main() {
    4    // hello.txtを開くのに失敗しました
    5    let f = File::open("hello.txt").expect("Failed to open hello.txt");
    6}
    • エラーメッセージは以下のように成る

      1thread 'main' panicked at 'Failed to open hello.txt: Error { repr: Os { code:
      22, message: "No such file or directory" } }', src/libcore/result.rs:906:4

エラーを委譲する
#

  • 関数内ではなく、関数の呼び出し元にエラーをどうするかを決めてもらう( 委譲 )

     1use std::io;
     2use std::io::Read;
     3use std::fs::File;
     4
     5fn read_username_from_file() -> Result<String, io::Error> {
     6    let f = File::open("hello.txt");
     7
     8    let mut f = match f {
     9        Ok(file) => file,
    10        Err(e) => return Err(e),
    11    };
    12
    13    let mut s = String::new();
    14
    15    match f.read_to_string(&mut s) {
    16        Ok(_) => Ok(s),
    17        Err(e) => Err(e),
    18    }
    19}
    • 1つめのファイル呼び出しが失敗しても、2つめの文字読み込みが失敗しても、呼び出し元の関数にエラーを委譲する

エラー委譲のショートカット: ? 演算子
#

  • 上記の処理は以下のように書ける

     1use std::io;
     2use std::io::Read;
     3use std::fs::File;
     4
     5fn read_username_from_file() -> Result<String, io::Error> {
     6    let mut f = File::open("hello.txt")?;
     7    let mut s = String::new();
     8    f.read_to_string(&mut s)?;
     9    Ok(s)
    10}
  • もっと省略するとこう。 ? 演算子の連結:

     1use std::io;
     2use std::io::Read;
     3use std::fs::File;
     4
     5fn read_username_from_file() -> Result<String, io::Error> {
     6    let mut s = String::new();
     7
     8    File::open("hello.txt")?.read_to_string(&mut s)?;
     9
    10    Ok(s)
    11}
  • ? を利用することで、定型コードの多くを排除できる

  • ただし、 ? 演算子が使えるのは、 return する型が Result の関数のみ

    • 以下はエラーとなる:

      1use std::fs::File;
      2
      3fn main() {
      4    let f = File::open("hello.txt")?;
      5}
      • エラーメッセージはこんな感じ:

         1error[E0277]: the trait bound `(): std::ops::Try` is not satisfied
         2(エラー: `(): std::ops::Try`というトレイト境界が満たされていません)
         3 --> src/main.rs:4:13
         4  |
         54 |     let f = File::open("hello.txt")?;
         6  |             ------------------------
         7  |             |
         8  |             the `?` operator can only be used in a function that returns
         9  `Result` (or another type that implements `std::ops::Try`)
        10  |             in this macro invocation
        11  |             (このマクロ呼び出しの`Result`(かまたは`std::ops::Try`を実装する他の型)を返す関数でしか`?`演算子は使用できません)
        12  |
        13  = help: the trait `std::ops::Try` is not implemented for `()`
        14  (助言: `std::ops::Try`トレイトは`()`には実装されていません)
        15  = note: required by `std::ops::Try::from_error`
        16  (注釈: `std::ops::Try::from_error`で要求されています)

ここに掲載する記事はまだありません。