rust - ¿Puedo escribir introspección con objetos de rasgo y luego bajarlo?
(2)
Tengo una colección de
Trait
, una función que itera sobre ella y hace algo, y luego me gustaría verificar el tipo de implementador y si es de tipo
Foo
, lo rechazo y llamo a algún método Foo.
Básicamente, algo similar al cambio de type-switch y la conversión de interfaz de Go.
Al buscar, encontré el
rasgo Any,
pero solo se puede implementar en
''static
tipos
''static
.
Para ayudar a demostrar lo que quiero:
let vec: Vec<Box<Trait>> = //
for e in vec.iter() {
e.trait_method();
// if typeof e == Foo {
// let f = e as Foo;
// f.foo_method();
//}
}
Como habrás notado, el downcasting solo funciona con
Any
rasgo, y sí, solo admite datos
''static
.
Puede encontrar una discusión reciente sobre por qué es así
here
.
Básicamente, implementar la reflexión para referencias de vidas arbitrarias es difícil.
También es imposible (a partir de ahora, al menos) combinar
Any
con su rasgo personalizado fácilmente.
Sin embargo, recientemente se ha creado una
macro biblioteca
para la implementación automática de
Any
para su rasgo.
También puede encontrar alguna discusión al respecto
here
.
Este no es un problema específico de Rust, aunque el vocabulario puede ser un poco diferente.
La forma ideal de resolver un problema como este, no solo con rasgos en Rust sino en cualquier idioma, es agregar el comportamiento deseado (
foo_method
en su ejemplo) a la interfaz abstracta (
Trait
):
trait Trait {
fn trait_method(&self);
fn foo_method(&self) {} // does nothing by default
}
struct Foo;
impl Trait for Foo {
fn trait_method(&self) {
println!("In trait_method of Foo");
}
fn foo_method(&self) { // override default behavior
println!("In foo_method");
}
}
struct Bar;
impl Trait for Bar {
fn trait_method(&self) {
println!("In trait_method of Bar");
}
}
fn main() {
let vec: Vec<Box<Trait>> = vec![Box::new(Foo), Box::new(Bar)];
for e in &vec {
e.trait_method();
e.foo_method();
}
}
En este ejemplo, he puesto una implementación predeterminada de
foo_method
en
Trait
que no hace nada, de modo que no tenga que definirla en cada
impl
sino solo en la (s) donde se aplica.
Realmente
debería intentar hacer que lo anterior funcione antes de recurrir a la conversión a un tipo concreto, que tiene serios inconvenientes que prácticamente borran las ventajas de tener objetos característicos en primer lugar.
Dicho esto, hay casos en los que puede ser necesario un downcasting, y Rust lo admite, aunque la interfaz es un poco torpe.
Puedes bajar
&Trait
a
&Foo
agregando un elenco intermedio a
&Any
:
use std::any::Any;
trait Trait {
fn as_any(&self) -> &Any;
}
struct Foo;
impl Trait for Foo {
fn as_any(&self) -> &Any {
self
}
}
fn downcast<T: Trait + ''static>(this: &Trait) -> Option<&T> {
this.as_any().downcast_ref()
}
as_any
tiene que ser un método en
Trait
porque necesita acceso al tipo concreto.
Ahora puede intentar llamar a los métodos
Foo
en un objeto de rasgo de
Trait
como este (
ejemplo de patio de recreo completo
):
if let Some(r) = downcast::<Foo>(trait_object_ref) {
r.foo_method();
}
Para que esto funcione, debe especificar qué tipo espera (
::<Foo>
) y usar
if let
manejar lo que sucede cuando el objeto referenciado no es una instancia de
Foo
.
No puede rechazar un objeto de rasgo a un tipo concreto a menos que sepa exactamente
qué
tipo concreto es.
¡Pero si alguna vez necesita saber el tipo concreto, los objetos característicos son casi inútiles de todos modos!
Probablemente debería usar una
enum
lugar, de modo que obtendrá errores en tiempo de compilación si omite manejar una variante en alguna parte.
Además, no puede usar
Any
con estructuras no
''static
, por lo que si algún
Foo
necesita contener una referencia, este diseño es un callejón sin salida.
La mejor solución, si puede hacerlo, es agregar
foo_method
al rasgo en sí.