Rust の Error の downcast で Box<dyn Error> からエラーを取り出す
前回は Error
トレイトの定義を見てみました 。今回は Error
の downcast
メソッドの挙動を見ようと思います。
ドクターメイト株式会社では Error
はもちろん使用しているのですが、 downcast
は意外と使用しておらず、 Box<dyn Error + Send + Sync>
はログ出力して終わり……のようになっていることが多いです。
downcast
は名前どおりにダウンキャストして中身を取り出せるのかを見ていこうかなと思います。
Error
の downcast
の定義
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>
で指定された T
を Box<T>
で返し、失敗すれば self
を返すものです。
Box<dyn Error>
を Box<T>
にダウンキャストしてくれるわけですね。
Error
の downcast
は 3 つ定義されている
Error
の downcast
は 3 つ定義されています。
impl dyn Error
impl dyn Error + Send
impl dyn Error + Send + Sync
Send
, Sync
のマーカートレイトの処理はしているものの中身はほとんど同じものです。なので impl dyn Error
のものを見ていきます。
Error
の downcast
を使ってみる
#[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::Error
に downcast
してみたところ、 Err
側になりました。そりゃそうですね。
Error
の downcast
の内部実装
Error
の downcast
の実装はざっくりと次のような形です。
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
を返す。
is
は downcast
に指定された T
の TypeId::of::<T>()
を取り、 self.type_id
の値と比較する。
self.type_id
は TypeId::of::<Self>()
。
ざっくり言うと std::any::TypeId
を使って型を比較している形です。
std::any::Any
の downcast
余談: downcast
の実装を見ていて「どこかで見たような実装だな」と思ったら std::any::Any
にも downcast
があり、そちらも似たような実装になっていました。
https://doc.rust-lang.org/std/any/trait.Any.html#method.downcast
std::any::Any
は crates:async-graphql の data を保持するのに使用されており、過去に調べていたようです。
次回予告
今回は Error
の downcast
メソッドを見てみました。 Box<dyn Error>
から元のエラーを取り出したいケースで使えるかもしれません。
次回は crates:thiserror の基本的な使い方をおさらいしようと思います。今回出てきているので順番が前後している気もしますが、まあ、いいでしょう!
Discussion