Подтвердить что ты не робот

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

Как мне получить Box<B> или &B или &Box<B> из переменной a в этом коде:

trait A {}

struct B;
impl A for B {}

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

Этот код возвращает ошибку:

non-scalar cast: Box<A> as Box<B>
4b9b3361

Ответ 1

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

use std::any::Any;

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

struct B;

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

fn main() {
    let a: Box<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 in Box<A> также реализует Any... что означает, что при вызове downcast_ref вы фактически вызываете его по типу объекта A. Any может только понижать значение к типу, на который он был вызван, что в данном случае равно A, поэтому вы можете вернуть только &A, который у вас уже был.

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

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

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

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

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

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

Ответ 2

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

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