😓

Rust の Error の downcast で Box<dyn Error> からエラーを取り出す

に公開

前回は Error トレイトの定義を見てみました 。今回は Errordowncast メソッドの挙動を見ようと思います。

ドクターメイト株式会社では Error はもちろん使用しているのですが、 downcast は意外と使用しておらず、 Box<dyn Error + Send + Sync> はログ出力して終わり……のようになっていることが多いです。

downcast は名前どおりにダウンキャストして中身を取り出せるのかを見ていこうかなと思います。

Errordowncast の定義

downcast は次のようなものです。

https://doc.rust-lang.org/std/error/trait.Error.html#method.downcast

pub fn downcast<T>(self: Box<dyn Error>) -> Result<Box<T>, Box<dyn Error>>
where
    T: Error + 'static,

self を取り、 Result<Box<T>, Box<dyn Error> を返します。

成功すれば downcast::<T> で指定された TBox<T> で返し、失敗すれば self を返すものです。

Box<dyn Error>Box<T> にダウンキャストしてくれるわけですね。

Errordowncast は 3 つ定義されている

Errordowncast は 3 つ定義されています。

  • impl dyn Error
  • impl dyn Error + Send
  • impl dyn Error + Send + Sync

Send, Sync のマーカートレイトの処理はしているものの中身はほとんど同じものです。なので impl dyn Error のものを見ていきます。

Errordowncast を使ってみる

https://github.com/bouzuya/rust-examples/blob/631406fe17b5cc74df1f55367af54945d7497d8f/box-downcast/src/main.rs

#[derive(Debug, thiserror::Error)]
enum MyError {
    #[error("invalid input")]
    InvalidInput,
    #[error("not found")]
    NotFound,
}

let b2: Box<dyn std::error::Error> = Box::new(MyError::InvalidInput);
match b2.downcast::<MyError>() {
    // e: Box<MyError>
    Ok(e) => println!(
        "{}",
        match *e {
            MyError::InvalidInput => "downcasted to MyError::InvalidInput",
            MyError::NotFound => "downcasted to MyError::NotFound",
        }
    ),
    // _e: Box<dyn std::error::Error>
    Err(_e) => unreachable!(),
}

let b2: Box<dyn std::error::Error> = Box::new(MyError::NotFound);
match b2.downcast::<std::io::Error>() {
    // _e: Box<std::io::Error>
    Ok(_e) => unreachable!(),
    // e: Box<dyn std::error::Error>
    Err(e) => println!("Failed to downcast: {}", e),
}

適当なエラー型 MyError を定義し、 Box<dyn Error>Box<MyError>downcast してみました。

Box<MyError> なので *e して Box を外していますね。

試しに中身と異なるエラー型 std::io::Errordowncast してみたところ、 Err 側になりました。そりゃそうですね。

Errordowncast の内部実装

Errordowncast の実装はざっくりと次のような形です。

pub fn downcast<T: Error + 'static>(self: Box<Self>) -> Result<Box<T>, Box<dyn Error>> {
    if self.is::<T>() {
        unsafe {
            let raw: *mut dyn Error = Box::into_raw(self);
            Ok(Box::from_raw(raw as *mut T))
        }
    } else {
        Err(self)
    }
}

pub fn is<T: Error + 'static>(&self) -> bool {
    // Get `TypeId` of the type this function is instantiated with.
    let t = TypeId::of::<T>();
    // Get `TypeId` of the type in the trait object (`self`).
    let concrete = self.type_id(private::Internal);
    // Compare both `TypeId`s on equality.
    t == concrete
}

fn type_id(&self, _: private::Internal) -> TypeId
where
    Self: 'static,
{
    TypeId::of::<Self>()
}

self.is::<T> を確認し、もし true なら中身を T として返し、そうでなければ self を返す。

isdowncast に指定された TTypeId::of::<T>() を取り、 self.type_id の値と比較する。

self.type_idTypeId::of::<Self>()

ざっくり言うと std::any::TypeId を使って型を比較している形です。

余談: std::any::Anydowncast

downcast の実装を見ていて「どこかで見たような実装だな」と思ったら std::any::Any にも downcast があり、そちらも似たような実装になっていました。

https://doc.rust-lang.org/std/any/trait.Any.html#method.downcast

std::any::Anycrates:async-graphql の data を保持するのに使用されており、過去に調べていたようです。

次回予告

今回は Errordowncast メソッドを見てみました。 Box<dyn Error> から元のエラーを取り出したいケースで使えるかもしれません。

次回は crates:thiserror の基本的な使い方をおさらいしようと思います。今回出てきているので順番が前後している気もしますが、まあ、いいでしょう!

GitHubで編集を提案
ドクターメイト

Discussion