Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compare enums only by variant, not value

Tags:

rust

I have an enum with the following structure:

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

where the 'members' of each variant are structs.

Now I want to compare if two enums have the same variant. So if I have

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

cmpvariant(a, b) should be true. I can imagine a simple double match code that goes through all the options for both enum instances. However, I am looking for a fancier solution, if it exists. If not, is there overhead for the double match? I imagine that internally I am just comparing two ints (ideally).

like image 403
Ben Ruijl Avatar asked Sep 13 '15 20:09

Ben Ruijl


People also ask

Can you compare enums in C++?

C++11 has introduced enum classes (also called scoped enumerations), that makes enumerations both strongly typed and strongly scoped. Class enum doesn't allow implicit conversion to int, and also doesn't compare enumerators from different enumerations.

What is enum variant?

The enum keyword allows the creation of a type which may be one of a few different variants. Any variant which is valid as a struct is also valid as an enum .

Can enums have methods Rust?

In Rust, methods cannot only be defined on structs, they can also be defined on tuples and enums, and even on built-in types like integers.


1 Answers

As of Rust 1.21.0, you can use std::mem::discriminant:

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

This is nice because it can be very generic:

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

Before Rust 1.21.0, I'd match on the tuple of both arguments and ignore the contents of the tuple with _ or ..:

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

I took the liberty of renaming the function though, as the components of enums are called variants, and really you are testing to see if they are equal, not comparing them (which is usually used for ordering / sorting).

For performance, let's look at the LLVM IR in generated by Rust 1.16.0 in release mode. The Rust Playground can show you this easily:

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 } 

The short version is that we do a switch on one enum variant, then compare to the other enum variant. It's overall pretty efficient, but I am surprised that it doesn't just directly compare the variant numbers. Perhaps this is something that an optimization pass could take care of?

If you wanted to have a macro to generate the function, something like this might be good start.

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

The macro does have limitations though - all the variants need to have a single variant. Supporting unit variants, variants with more than one type, struct variants, visibility, etc are all real hard. Perhaps a procedural macro would make it a bit easier.

like image 101
Shepmaster Avatar answered Oct 05 '22 21:10

Shepmaster