rust - orden - ¿Cómo obtener una referencia a un tipo concreto de un objeto de rasgo?
lista de etiquetas html (2)
Debe quedar claro que la conversión puede fallar si hay otro tipo
C
implementa
A
e intenta convertir la
Box<C>
en una
Box<B>
.
No sé su situación, pero para mí parece que está trayendo técnicas de otros lenguajes, como Java, a Rust.
Nunca me he encontrado con este tipo de problema en Rust, tal vez el diseño de su código podría mejorarse para evitar este tipo de lanzamiento.
Si lo desea, puede "emitir" prácticamente cualquier cosa con
mem::transmute
.
Lamentablemente, tendremos un problema si solo queremos lanzar el
Box<A>
al
Box<B>
o
&A
a
&B
porque un puntero a un
trait
es un indicador gordo que en realidad consiste en dos punteros: uno al objeto real, uno a la vptr.
Si lo estamos convirtiendo en un tipo de
struct
, podemos ignorar el vptr.
Por favor recuerde que esta solución es altamente insegura y bastante obscena, no la usaría en código "real".
let (b, vptr): (Box<B>, *const ()) = unsafe { std::mem::transmute(a) };
EDITAR: Al diablo con eso, es aún más inseguro de lo que pensaba.
Si desea hacerlo correctamente de esta manera, tendría que usar
std::raw::TraitObject
.
Sin embargo, esto todavía es inestable.
No creo que esto sea útil para OP;
no lo uses!
Hay mejores alternativas en esta pregunta muy similar: cómo hacer coincidir los implementadores de rasgos
¿Cómo obtengo
Box<B>
o
&B
o
&Box<B>
de la variable en este código:
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>;
}
Este código devuelve un error:
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
Hay dos formas de hacer downcasting en Rust.
El primero es usar
Any
.
Tenga en cuenta que esto
solo le
permite bajar al tipo de concreto original exacto.
Al igual que:
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!"),
};
}
La otra forma es implementar un método para cada "objetivo" en el rasgo base (en este caso,
A
) e implementar los lanzamientos para cada tipo de objetivo deseado.
Espera, ¿por qué necesitamos
as_any
?
Incluso si agrega
Any
como requisito para
A
, todavía no funcionará correctamente.
El primer problema es que la
A
en la
Box<dyn A>
también
implementará
Any
... lo que significa que cuando llame a
downcast_ref
, en realidad lo llamará en el tipo de objeto
A
Any
solo
puede bajar al tipo en el que se invocó, que en este caso es
A
, por lo que solo podrá volver a lanzar a
&dyn A
que ya tenía.
Pero hay una implementación de
Any
para el tipo subyacente en
algún lugar
, ¿verdad?
Bueno, sí, pero no puedes llegar a eso.
Rust no te permite "realizar una conversión cruzada" de
&dyn A
a
&dyn Any
.
Para eso
es para lo que sea;
como es algo que solo se implementa en nuestros tipos "concretos", el compilador no se confunde con respecto a cuál se supone que debe invocar.
Invocarlo en un
&dyn A
hace que se
B::as_any
dinámicamente a la implementación concreta (de nuevo, en este caso,
B::as_any
), que devuelve un
&dyn Any
utilizando la implementación de
Any
for
B
, que es lo que queremos.
Tenga en cuenta que
puede
evadir todo este problema simplemente no utilizando
A
en absoluto
.
Específicamente, lo siguiente
también
funcionará:
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!")
};
}
Sin embargo, esto le impide tener otros métodos; todo lo que puedes hacer aquí es abatido a un tipo concreto.
Como nota final de potencial interés, la caja de
mopa
permite combinar la funcionalidad de
Any
con un rasgo propio.