Comparar enums apenas por variante, sem valor

Eu tenho um enum com a seguinte estrutura:

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

onde os ‘membros’ de cada variante são estruturas.

Agora quero comparar se duas enums tiverem a mesma variante. Então, se eu tiver

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

cmpvariant(a, b) deve ser true . Eu posso imaginar um código de match dupla simples que passa por todas as opções para ambas as instâncias de enum. No entanto, estou procurando uma solução mais sofisticada, se existir. Se não, há sobrecarga para o duplo jogo? Eu imagino que internamente eu estou apenas comparando dois ints (idealmente).

A partir do Rust 1.21.0, você pode usar std::mem::discriminant :

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

Isso é legal porque pode ser muito genérico:

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

Antes do Rust 1.21.0, eu combinaria na tupla dos dois argumentos e ignoraria o conteúdo da tupla com _ ou .. :

 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)); } 

Eu tomei a liberdade de renomear a function, já que os componentes de enums são chamados de variantes , e realmente você está testando para ver se eles são iguais, não comparando-os (o que geralmente é usado para ordenação / ordenação).

Para desempenho, vamos ver o IR da LLVM gerado pelo Rust 1.16.0 no modo de liberação. O Rust Playground pode mostrar isso 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 } 

A versão curta é que fazemos uma mudança em uma variante enum e, em seguida, comparamos com a outra variante enum. Em geral, é bastante eficiente, mas me surpreende que não apenas compare diretamente os números das variantes. Talvez isso seja algo que um passe de otimização possa resolver?

Se você quisesse ter uma macro para gerar a function, algo assim poderia ser um bom começo.

 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)); } 

A macro tem limitações – todas as variantes precisam ter uma única variante. Suportar variantes de unidades, variantes com mais de um tipo, variantes de estruturas, visibilidade, etc. são realmente difíceis . Talvez uma macro procedural torne isso um pouco mais fácil.