web-dev-qa-db-ja.com

特性オブジェクトから具象型への参照を取得する方法は?

Box<B>または&Bまたは&Box<B>このコードのa変数から:

trait A {}

struct B;
impl A for B {}

fn main() {
    let mut a: Box<dyn A> = Box::new(B);
    let b = a as Box<B>;
}

このコードはエラーを返します:

error[E0605]: non-primitive cast: `std::boxed::Box<dyn A>` as `std::boxed::Box<B>`
 --> src/main.rs:8:13
  |
8 |     let b = a as Box<B>;
  |             ^^^^^^^^^^^
  |
  = note: an `as` expression can only be used to convert between primitive types. Consider using the `From` trait
33
Aleksandr

Rustでダウンキャストを行うには2つの方法があります。最初は Any を使用することです。このonlyを使用すると、元の正確なコンクリートタイプにダウンキャストできます。そのようです:

use std::any::Any;

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

struct B;

impl A for B {
    fn as_any(&self) -> &dyn Any {
        self
    }
}

fn main() {
    let a: Box<dyn A> = Box::new(B);
    // The indirection through `as_any` is because using `downcast_ref`
    // on `Box<A>` *directly* only lets us downcast back to `&A` again.
    // The method ensures we get an `Any` vtable that lets us downcast
    // back to the original, concrete type.
    let b: &B = match a.as_any().downcast_ref::<B>() {
        Some(b) => b,
        None => panic!("&a isn't a B!"),
    };
}

もう1つの方法は、基本特性(この場合はA)の各「ターゲット」にメソッドを実装し、目的の各ターゲットタイプにキャストを実装することです。


待って、なぜas_anyが必要なのですか?

Anyの要件としてAを追加しても、まだ正しく機能しません。最初の問題は、Box<dyn A>AalsoAny...を実装するということです。つまり、downcast_refを呼び出すと、実際にオブジェクト型Aで呼び出しています。 Any canonly呼び出された型にダウンキャストします。この場合はAなので、できるのは既に持っていた&dyn Aに戻すには。

しかし、somewhereに、基礎となる型のAnyの実装がありますか?まあ、はい、しかしあなたはそれを得ることができません。 Rustは、&dyn Aから&dyn Anyへの「クロスキャスト」を許可しません。

Thatas_anyの目的です。これは「具象」型にのみ実装されているものであるため、コンパイラはどの型を呼び出すべきかについて混乱することはありません。 &dyn Aで呼び出すと、具体的な実装(この場合もB::as_any)に動的にディスパッチされ、Any for Bの実装を使用して&dyn Anyが返されます。欲しいです。

canAat allを使用しないことで、この問題全体を回避できます。 。具体的には、alsoが機能します。

fn main() {
    let a: Box<dyn Any> = Box::new(B);
    let _: &B = match a.downcast_ref::<B>() {
        Some(b) => b,
        None => panic!("&a isn't a B!")
    };    
}

ただし、これにより、otherメソッドを使用できなくなります。allここでできることは、具体的なタイプにダウンキャストされます。

潜在的な関心の最後のメモとして、 mopa crateを使用すると、Anyの機能と独自の特性を組み合わせることができます。

48
DK.

Cを実装する別のタイプAがあり、Box<C>Box<B>にキャストしようとすると、キャストが失敗する可能性があることは明らかです。あなたの状況はわかりませんが、Javaのような他の言語の技術をRustに持ち込んでいるように見えます。 Rust-この種の問題を回避するためにコード設計を改善できるかもしれません。

必要に応じて、 mem::transmute でほとんど何でも「キャスト」できます。悲しいことに、Box<A>Box<B>にキャストしたい場合、または&A&Bにキャストしたい場合、traitへのポインターが太いため、問題が発生します。 -実際には2つのポインターで構成されるポインター:1つは実際のオブジェクトへ、もう1つはvptrへ。 struct型にキャストする場合、vptrを無視できます。このソリューションは非常に安全ではなく、かなりハッキングされていることを覚えておいてください。「実際の」コードでは使用しません。

let (b, vptr): (Box<B>, *const ()) = unsafe { std::mem::transmute(a) };

編集:それを台無しに、それは私が思ったよりもさらに安全ではありません。このように正しく行うには、 std::raw::TraitObject を使用する必要があります。しかし、これはまだ不安定です。これがOPに役立つとは思いません。使用しないでください!

この非常によく似た質問には、より良い代替案があります。 特性実装者との一致方法

4