Как получить ссылку на конкретный тип из объекта черты?
Как я могу получить 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; не используйте это!
В этом очень похожем вопросе есть лучшие альтернативы: как подобрать черты разработчиков