Confronta enumerazioni solo per variante, non valore

Ho un enum con la seguente struttura:

enum Expression { Add(Add), Mul(Mul), Var(Var), Coeff(Coeff) } 

dove i ‘membri’ di ogni variante sono structs.

Ora voglio confrontare se due enumerazioni hanno la stessa variante. Quindi se ho

 let a = Expression::Add({something}); let b = Expression::Add({somethingelse}); 

cmpvariant(a, b) dovrebbe essere true . Posso immaginare un semplice codice di doppia match che passa attraverso tutte le opzioni per entrambe le istanze di enum. Tuttavia, sto cercando una soluzione più elaborata, se esiste. In caso contrario, c’è un sovraccarico per la partita doppia? Immagino che internamente sto solo confrontando due inte (idealmente).

A partire da Rust 1.21.0, puoi usare std::mem::discriminant :

 fn variant_eq(a: &Op, b: &Op) -> bool { std::mem::discriminant(a) == std::mem::discriminant(b) } 

Questo è bello perché può essere molto generico:

 fn variant_eq(a: &T, b: &T) -> bool { std::mem::discriminant(a) == std::mem::discriminant(b) } 

Prima di Rust 1.21.0, avrei trovato la tupla di entrambi gli argomenti e ignorato il contenuto della tupla con _ o .. :

 struct Add(u8); struct Sub(u8); enum Op { Add(Add), Sub(Sub), } fn variant_eq(a: &Op, b: &Op) -> bool { match (a, b) { (&Op::Add(..), &Op::Add(..)) => true, (&Op::Sub(..), &Op::Sub(..)) => true, _ => false, } } fn main() { let a = Op::Add(Add(42)); let b = Op::Add(Add(42)); let c = Op::Add(Add(21)); let d = Op::Sub(Sub(42)); println!("{}", variant_eq(&a, &b)); println!("{}", variant_eq(&a, &c)); println!("{}", variant_eq(&a, &d)); } 

Tuttavia, mi sono permesso di rinominare la funzione, dato che i componenti dell’enumerazione sono chiamati varianti , e in realtà stai verificando se sono uguali, non confrontandoli (che di solito è usato per ordinare / ordinare).

Per prestazioni, diamo un’occhiata a LLVM IR in generato da Rust 1.16.0 in modalità di rilascio. Il Rust Playground può mostrarlo facilmente:

 define internal fastcc zeroext i1 @_ZN10playground10variant_eq17h3a88b3837dfe66d4E(i8 %.0.0.val, i8 %.0.0.val1) unnamed_addr #0 { entry-block: %switch2 = icmp eq i8 %.0.0.val, 1 %switch = icmp ne i8 %.0.0.val1, 1 br i1 %switch2, label %bb5, label %bb4 bb3: ; preds = %bb5, %bb4 br label %bb6 bb4: ; preds = %entry-block br i1 %switch, label %bb6, label %bb3 bb5: ; preds = %entry-block br i1 %switch, label %bb3, label %bb6 bb6: ; preds = %bb5, %bb4, %bb3 %_0.0 = phi i1 [ false, %bb3 ], [ true, %bb4 ], [ true, %bb5 ] ret i1 %_0.0 } 

La versione breve è che eseguiamo un passaggio su una variante enum, quindi confrontiamo con l’altra variante enum. Nel complesso è abbastanza efficiente, ma sono sorpreso che non si confronti solo direttamente i numeri varianti. Forse è qualcosa di cui potrebbe occuparsi un passaggio di ottimizzazione?

Se si desidera avere una macro per generare la funzione, qualcosa di simile potrebbe essere un buon inizio.

 struct Add(u8); struct Sub(u8); macro_rules! foo { (enum $name:ident { $($vname:ident($inner:ty),)* }) => { enum $name { $($vname($inner),)* } impl $name { fn variant_eq(&self, b: &Self) -> bool { match (self, b) { $((&$name::$vname(..), &$name::$vname(..)) => true,)* _ => false, } } } } } foo! { enum Op { Add(Add), Sub(Sub), } } fn main() { let a = Op::Add(Add(42)); let b = Op::Add(Add(42)); let c = Op::Add(Add(21)); let d = Op::Sub(Sub(42)); println!("{}", Op::variant_eq(&a, &b)); println!("{}", Op::variant_eq(&a, &c)); println!("{}", Op::variant_eq(&a, &d)); } 

La macro però ha delle limitazioni: tutte le varianti devono avere una singola variante. Le varianti di unità di supporto, le varianti con più di un tipo, le varianti di struttura, la visibilità, ecc. Sono tutte molto difficili . Forse una macro procedurale lo renderebbe un po ‘più semplice.