Как получить ссылку на конкретный тип из объекта черты?

Как я могу получить 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

2 ответа

Решение

Есть два способа сделать downcasting в Rust. Первый заключается в использовании Any, Обратите внимание, что это позволяеттолько снизить точный исходный конкретный тип. Вот так:

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!"),
    };
}

Другой способ - реализовать метод для каждой "цели" базовой черты (в этом случаеA) и реализовать приведение типов для каждого желаемого типа цели.


Подожди, зачем намas_any?

Даже если вы добавите Any как требование к AЭто все еще не будет работать правильно. Первая проблема заключается в том, что A в Box<dyn A> также будет реализовывать Any... это означает, что когда вы звоните downcast_refвы на самом деле будете вызывать его для типа объекта A, Any может быть понижен только до того типа, к которому он был вызван, что в данном случае AТаким образом, вы сможете только вернуться к &dyn A который вы уже имели.

Но есть реализация Any для базового типа где-то там, верно? Ну, да, но вы не можете получить это. Ржавчина не позволяет вам "перекрестный бросок" из &dyn A в &dyn Any,

Вот что as_any для; поскольку это что-то, реализованное только в наших "конкретных" типах, компилятор не запутывается в том, какой именно он должен вызывать. Называя это на &dyn A заставляет его динамически отправлять в конкретную реализацию (опять же, в этом случае, B::as_any), который возвращает &dyn Any используя реализацию Any за B, чего мы и хотим.

Обратите внимание, что вы можете обойти эту проблему, просто не используя A на всех. В частности, следующее также будет работать:

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!")
    };    
}

Тем не менее, это лишает вас каких-либо других методов; все, что вы можете сделать здесь, это опускаться до конкретного типа.

В качестве последнего замечания потенциального интереса, мопакрат позволяет объединить функциональность Any с собственной чертой.

Должно быть ясно, что приведение может потерпеть неудачу, если есть другой тип C реализации A и вы пытаетесь разыграть Box<C> в Box<B>, Я не знаю вашей ситуации, но мне кажется, что вы привносите методы из других языков, например Java, в Rust. Я никогда не сталкивался с такой проблемой в Rust - возможно, ваш код может быть улучшен, чтобы избежать такого рода приведения.

Если вы хотите, вы можете "бросить" почти все с mem::transmute, К сожалению, у нас будет проблема, если мы просто хотим бросить Box<A> в Box<B> или же &A в &B потому что указатель на trait это толстый указатель, который на самом деле состоит из двух указателей: один на реальный объект, другой на vptr. Если мы приведем его к struct типа, мы можем просто игнорировать vptr. Пожалуйста, помните, что это решение очень небезопасно и довольно хакерски - я бы не использовал его в "реальном" коде.

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

РЕДАКТИРОВАТЬ: Винт это, это даже более небезопасно, чем я думал. Если вы хотите сделать это правильно, вам придется использовать std::raw::TraitObject, Это все еще нестабильно, хотя. Я не думаю, что это полезно для OP; не используйте это!

В этом очень похожем вопросе есть лучшие альтернативы: как подобрать черты разработчиков

Другие вопросы по тегам