🦀

Box<dyn error::Error>とは?

2023/11/28に公開

登場人物

  • Box: ヒープ上にデータを格納するためのスマートポインタ
  • dyn: dynamicの略。実行時にトレイトの具体的な型を決めたいときに使う
  • error::Error: Rustの標準ライブラリのerrorモジュールにあるErrorトレイト

用語説明

  • ヒープ: ヒープメモリ、スタックメモリと違い動的に割り当てられる。実行時にサイズ決定されるようなものはヒープで、コンパイル時に決まるものはスタックという感じ。
  • スマートポインタ: Rustの文脈ではメモリや所有権を扱う概念・モノを指す。例えばBox<T>はTをヒープ上で扱うためのスマートポインタといえる。
  • トレイト: 振る舞いを定義するためのもの。例えばErrorトレイトはエラーの説明を提供するdescriptionメソッドや、エラーの原因を返すcauseメソッドなどがある。

解説

fn test() -> Result<(), Box<dyn error::Error>> {
	Ok(())
}

よくあるこのようなエラーの定義は「Errorトレイトの実装を持つ何らかのエラーを返す」ということです。

単純にErrorトレイトを返すような定義をすることはできません、これはトレイト自体は具体的なサイズを持たないためです。

そのためdynキーワードを指定し、動的ディスパッチ。つまり実行時にErrorトレイトの具体的な型が何であるか決定されるようにします(コンパイル時ではなく)。

そして最後にそれをBoxで囲むことで、dynキーワードが指定されたErrorトレイトは、実行時に具体的な型が決定され、それがBoxによってヒープメモリに割り当てられることになります。

つまり最初にエラーの型が決まってないので実行時に決まるようにしたいというニーズがあり、それを実現するのがBox<dyn error::Error>ということです。

エラーの定義

Box<dyn error::Error>はErrorトレイトを持つ何かという形になっており、汎用性の高い何かを作っているのでなければ、お作法的にはエラー型を定義しそれを返すような形で書いたほうが良かったりはします。

よくあるエラー定義はenumを使うような形な気がします。chatgpt君にサンプルコードを書いてもらいました。

use std::error::Error;
use std::fmt;

// カスタムエラーenumの定義
#[derive(Debug)]
enum MyError {
    ErrorType1,
    ErrorType2,
}

// Errorトレイトの実装
impl Error for MyError {}

// Displayトレイトの実装
impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            MyError::ErrorType1 => write!(f, "Error Type 1 occurred"),
            MyError::ErrorType2 => write!(f, "Error Type 2 occurred"),
        }
    }
}

// Result型を返す関数の定義
fn do_something(flag: bool) -> Result<(), MyError> {
    if flag {
        Ok(())
    } else {
        Err(MyError::ErrorType1)
    }
}

fn main() {
    match do_something(false) {
        Ok(_) => println!("Success!"),
        Err(e) => println!("An error occurred: {}", e),
    }
}

他にも構造体を使用するケースや既存のエラー型のラッパーを作成するケースもありますが、本筋から外れるので割愛します。

上記サンプルコードはRust Playgroundで簡単に動作を確認することができるので気になる方はチェックしてみてください。

https://play.rust-lang.org/

Discussion