rust

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í.