🦀

100日後にRustをちょっと知ってる人になる: [Day 49]エラー処理

2022/10/18に公開

Day 49 のテーマ

気がつけば、もう 49 日もRust を少しずつ勉強してきたことに気づきました。まだまだ「理解した!」というには程遠いですけれど、基本的な制御系の文法などは分かってきてるような気がしています。
さて、ここまで作るというところにフォーカスして学んできているのですが、プログラムをを作っていく上では見逃すことができないエラー処理について、この折返し地点で見ておこうと思います。

エラー処理

Rust は高信頼性ということを謳い文句にしているプログラム言語であることは、今まで見てきた中で十分に分かってきました。と入っても、プログラムなのでエラーの発生ということは必ずあります。
Rust では、そのエラーの発生やエラー処理に対しても信頼性を高く考慮している、と言われています。

ところで、Java を思い浮かべてください。異常系のハンドリングというと次の 2 つを考えると思います。

  • java.lang.Exception
  • java.lang.Error

つまり、例外エラーという異常な状態です。例外とエラーの違いについては、プログラム言語によって微妙に解釈が異なる場合があります。多くの場合は次のような解釈となっているのではないでしょうか。

  • 例外
    • 修正するとなおるものやプログラム的に回避できる異常
  • エラー
    • プログラムが正常に動作しない
    • 異常終了する
      • コンパイルエラ
      • 実行時エラー

Rust には例外が存在しません。Rust にはエラーしか存在しません。ただし、エラーには次の 2 つの分類があります。

  • 回復不能なエラー
  • 回復可能なエラー

回復不能なエラー

回復不能なエラーとは、例えば配列の境界を越えたところにアクセスしようとするような場合です。回復ができないようなエラーが発生した場合は、処理を継続するわけにはいかないため強制終了するしかありません。

panic!

強制終了する場合に何も形跡を残さずに終了してしまうと、何が原因か、どこで異常終了したのかを追跡できなくなります。そこで、メッセージを表示してプログラムを強制終了させるシンプルな手段なが panic! マクロです。

以下のように panic! マクロを使ってみます。

fn main() {
    println!("処理してます - 1");
    panic!("Panicしました!");
    println!("処理してます - 2");
}

いかのエラーメッセージを見てもらうと分かりますが、panic! マクロが呼ばれた時点で処理は強制終了されています。
そのため、panic! の直後にある println!("処理してます - 2"); は到達不能となっています。

  |
3 |     panic!("Panicしました!");
  |     ------------------------- any code following this expression is unreachable
4 |     println!("処理してます - 2");
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unreachable statement
  |
  = note: `#[warn(unreachable_code)]` on by default
  = note: this warning originates in the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)

回復可能なエラー

回復可能なエラーとは、いろいろなところでも例として挙げられていますが、既存のファイルを開こうとした時にファイルが存在しなかったような場合です。ここで、なぜ回復可能かと言うと次のような手段が考えられるからです。

  • ファイルが見つからないエラーが発生したら
    • 正しいファイルを指定し直す
    • 新しいファイルを作成する
    • など

このようにエラーが発生しても、プログラムの考慮次第で処理を継続することができるようなものです。
そのため、ある関数のある場所でエラーが発生したら、そのエラーが発生した関数を呼び出した元に知らせる仕組みが必要になります。
それが、Result 型です。

Result 型

Result 型がどのようなものか見ていきます。

次の定義を見てもらうと分かるように、Result は、成功(Ok)または失敗(Err)を表す列挙型です。

  • Ok<T>: 要素 T が見つかった場合
  • Err<E>: 要素 E とともにエラーが見つかった場合
pub enum Result<T, E> {
    Ok(T),
    Err(E),
}

Result には多くのメソッドが定義されています。

例えば、次の unwrap() メソッドは、Ok を返すか、Err の場合は panic を発生させます。ただし、エラーメッセージはデフォルトのものになります。

また次の expect() メソッドは、unwrap() メソッドと異なり指定したエラーメッセージを panic 時に出力することができます。

そして次の expect_err() メソッドは、expect() の逆で Err を返す、つまり意図的に panic を発生させたりするような時に用いられます。

Day 49 のまとめ

どのようなプログラム言語でもエラーハンドリングは重要です。信頼性が特徴になっている Rust であってもそれは変わりません。
エラーハンドリングを適切に行い、より安全なコードを書くことができるようになるように、今日確認をした 回復可能なエラー回復不可能なエラーについて扱い方を今後も学ぼうと思います。

また、手段としての次の仕組みを十分に使いこなせるように意識していきたいと思いました。

  • panic! マクロ
  • Result
GitHubで編集を提案

Discussion