Compara las enumeraciones solo por variante, no valor

Tengo una enumeración con la siguiente estructura:

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

donde los ‘miembros’ de cada variante son estructuras.

Ahora quiero comparar si dos enumeraciones tienen la misma variante. Entonces si tengo

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

cmpvariant(a, b) debe ser true . Puedo imaginar un código de doble match simple que revisa todas las opciones para ambas instancias enum. Sin embargo, estoy buscando una solución más elegante, si existe. Si no, ¿hay gastos generales para la doble coincidencia? Imagino que internamente estoy comparando dos ints (idealmente).

A partir de Rust 1.21.0, puede usar std::mem::discriminant :

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

Esto es bueno porque puede ser muy genérico:

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

Antes de Rust 1.21.0, coincidiría en la tupla de ambos argumentos e ignoraría el contenido de la 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)); } 

Sin embargo, me tomé la libertad de cambiar el nombre de la función, ya que los componentes de las enumeraciones se llaman variantes , y realmente estás probando para ver si son iguales, no comparándolos (que generalmente se usa para ordenar / ordenar).

Para el rendimiento, veamos el LLVM IR generado por Rust 1.16.0 en modo de lanzamiento. The Rust Playground puede mostrarte esto fácilmente:

 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 versión corta es que hacemos un cambio en una variante de enumeración, luego lo comparamos con la otra variante enum. En general es bastante eficiente, pero me sorprende que no solo compare directamente los números variantes. Quizás esto es algo de lo que podría ocuparse un pase de optimización.

Si quisiera tener una macro para generar la función, algo así podría ser un buen comienzo.

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

Sin embargo, la macro tiene limitaciones: todas las variantes deben tener una sola variante. Las variantes de unidades de soporte, las variantes con más de un tipo, las variantes de estructura, la visibilidad, etc. son muy difíciles . Quizás una macro de procedimiento lo haría un poco más fácil.