Ответ 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
с вашей собственной чертой.