Posso digitare l’introspezione con gli oggetti tratto e poi declassarlo?

Ho una collezione di Trait , una funzione che esegue iterazioni su di esso e fa qualcosa, quindi vorrei controllare il tipo di implementatore e se è di tipo Foo quindi lo downcast e chiamo un metodo Foo.

Fondamentalmente, qualcosa di simile alla conversione di tipo switch e interfaccia di Go.

Cercando in giro ho trovato il tratto Any, ma può essere implementato solo sui tipi 'static .

Per aiutare a dimostrare ciò che voglio:

 let vec: Vec<Box> = // for e in vec.iter() { e.trait_method(); // if typeof e == Foo { // let f = e as Foo; // f.foo_method(); //} } 

Come avrai notato, il downcasting funziona solo con Any tratto, e sì, supporta solo 'static dati 'static . Puoi trovare una discussione recente sul perché è così qui . Fondamentalmente, implementare la riflessione per i riferimenti di vite arbitrarie è difficile.

È anche imansible (almeno come ora) combinare Any con il tuo tratto personalizzato facilmente. Tuttavia, è stata recentemente creata una libreria di macro per l’implementazione automatica di Any per il tuo tratto. Puoi anche trovare qualche discussione su di esso qui .

Questo non è un problema specifico della Ruggine, anche se il vocabolario potrebbe essere un po ‘diverso. Il modo ideale per risolvere un problema come questo, non solo con i tratti in Rust ma in qualsiasi linguaggio, è aggiungere il comportamento desiderato ( foo_method nel tuo esempio) all’interfaccia astratta ( 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> = vec![Box::new(Foo), Box::new(Bar)]; for e in &vec { e.trait_method(); e.foo_method(); } } 

In questo esempio, ho inserito un’implementazione predefinita di foo_method in Trait che non fa nulla, in modo da non foo_method definire in ogni impl ma solo nella (e) dove si applica. Dovresti davvero provare a fare il lavoro di cui sopra prima di ricorrere al downcasting su un tipo concreto, che ha seri inconvenienti che cancellano tutti i vantaggi di avere oggetti tratti.

Detto questo, ci sono casi in cui il downcasting può essere necessario, e Rust lo supporta, anche se l’interfaccia è un po ‘goffo. Puoi downcast &Trait to &Foo aggiungendo un cast 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(this: &Trait) -> Option<&T> { this.as_any().downcast_ref() } 

as_any deve essere un metodo in Trait perché ha bisogno di accedere al tipo concreto. Ora puoi provare a chiamare i metodi di Foo su un object tratto Trait come questo ( esempio completo di campo da gioco ):

 if let Some(r) = downcast::(trait_object_ref) { r.foo_method(); } 

Per fare in modo che funzioni, devi specificare il tipo che ti aspetti ( :: ) e usarlo if let di gestire ciò che accade quando l’object di riferimento non è un’istanza di Foo . Non è ansible downcast un object tratto a un tipo concreto a meno che non si sappia esattamente di quale tipo concreto sia.

Ma se hai mai bisogno di conoscere il tipo concreto, gli oggetti tratti sono quasi inutili comunque! Probabilmente dovresti usare un enum , in modo da ottenere degli errori in fase di compilazione se ometti di gestire una variante da qualche parte. Inoltre, non è ansible usare Any con strutture non 'static , quindi se qualche Foo potrebbe dover contenere un riferimento, questo design è un vicolo cieco. La soluzione migliore, se puoi farlo, è aggiungere foo_method al tratto stesso.