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 Errorimpl dyn Error + Sendimpl 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 の基本的な使い方をおさらいしようと思います。今回出てきているので順番が前後している気もしますが、まあ、いいでしょう!
追記: 次回『 thiserror crate の基本的な使い方』を書きました。
Discussion