dyn 型をアップキャストするとエラーになる。

サンプル

以下では、dyn 型から dyn 型へのキャストでエラーになっている。


fn main() {
    let some = &0 as &dyn SomeTrait;
    let _base = some as &dyn BaseTrait;
}

// -- BaseTrait -- //

trait BaseTrait {}

impl<T> BaseTrait for T {}

// -- SomeTrait -- //

trait SomeTrait: BaseTrait {}

impl<T: BaseTrait> SomeTrait for T {}

error[E0658]: cannot cast `dyn SomeTrait` to `dyn BaseTrait`, trait upcasting coercion is experimental
 --> src\main.rs:3:16
  |
3 |     let _base = some as &dyn BaseTrait;
  |                 ^^^^
  |
  = note: see issue #65991 <https://github.com/rust-lang/rust/issues/65991> for more information
  = note: required when coercing `&dyn SomeTrait` into `&dyn BaseTrait`

解決策

幾つか方法がある。

※ 方法 3 が最も汎用的なので迷ったらそれを選ぶとよい。

方法 1

基本となる戦略

アップキャスト用のメソッドを派生側のトレイトに作る。

つまり、キャストの位置をトレイトの実装内へと移す。これにより、キャスト対象の静的型が分かっている箇所でキャストできるため、静的型から動的型へのキャストとなり、エラーにはならなくなる。

サンプル

以下では、SomeTrait にアップキャスト用のメソッド as_base を定義している。


fn main() {
    let some = &0 as &dyn SomeTrait;
    let _base = some.as_base();
}

// -- BaseTrait -- //

trait BaseTrait {}

impl<T> BaseTrait for T {}

// -- SomeTrait -- //

trait SomeTrait: BaseTrait {
    fn as_base(&self) -> &dyn BaseTrait;
}

impl<T: BaseTrait> SomeTrait for T {
    fn as_base(&self) -> &dyn BaseTrait {
        self
    }
}

方法 2

基底側でのアップキャスト

アップキャスト用メソッドを派生側ではなく基底側に作っておく。

この方法は、複数の派生トレイトに楽に対応できる。

ただし、基底側が定義済だと使えない。

サンプル

以下では、BaseTrait にアップキャスト用のメソッド as_base を定義している。


fn main() {
    let some = &0 as &dyn SomeTrait;
    let _base = some.as_base();
}

// -- BaseTrait -- //

trait BaseTrait {
    fn as_base(&self) -> &dyn BaseTrait;
}

impl<T> BaseTrait for T {
    fn as_base(&self) -> &dyn BaseTrait {
        self
    }
}

// -- SomeTrait -- //

trait SomeTrait: BaseTrait {}

impl<T: BaseTrait> SomeTrait for T {}

方法 3

アップキャスト用トレイト

予めアップキャスト用のトレイトを作っておく。

この方法は、複数の派生トレイトに楽に対応できる。

また、基底側が定義済でも使える。

サンプル

以下では、定義済の Any へのアップキャスト用に AsAny を用意している。


use std::any::Any;

fn main() {
    let some = &0 as &dyn SomeTrait;
    let _any = some.as_any();
}

// -- AsAny -- //

trait AsAny: Any {
    fn as_any(&self) -> &dyn Any;
}

impl<T: Any> AsAny for T {
    fn as_any(&self) -> &dyn Any {
        self
    }
}

// -- SomeTrait -- //

trait SomeTrait: Any + AsAny {
    fn test(&self) {
        let _any = self.as_any();
    }
}

impl<T: Any> SomeTrait for T {}

発展形

上記では、不変参照を通したアップキャストについて説明したが、他にも可変参照や Box を通した関数があると便利 (Box 以外のスマートポインタ、例えば Rc などは、Box から From トレイトで変換できるため、事前に関数を用意しておく必要性は低い)。

サンプル

以下では、前述の『方法 3』のサンプルの AsAny を強化している。


trait AsAny: Any {
    fn as_any(&self) -> &dyn Any;
    fn as_any_mut(&mut self) -> &mut dyn Any;
    fn as_any_box(self: Box<Self>) -> Box<dyn Any>;
}

impl<T: Any> AsAny for T {
    fn as_any(&self) -> &dyn Any {
        self
    }

    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }

    fn as_any_box(self: Box<Self>) -> Box<dyn Any> {
        self
    }
}