7

I have this enum:

enum MyEnum {
    Var1,
    Var2(u32),
    Var3(u32, u32),
}

and I want to achieve the following behavior:

let mut set = HashSet::new();
set.insert(MyEnum::Var1);
set.insert(MyEnum::Var1);
set.insert(MyEnum::Var2(1));
set.insert(MyEnum::Var2(2));
set.insert(MyEnum::Var3(1, 1));
set.insert(MyEnum::Var3(1, 1));
set.insert(MyEnum::Var3(2, 1));
set.insert(MyEnum::Var3(2, 2));

println!("set = {:?}", set);
// set = {Var1, Var2(1), Var2(2), Var3(1, 1), Var3(2, 1)}

That is, I want to change the Hash behavior for variant Var3 specifically to depend on its first u32 only.

Importantly, I don't want to exhaustively match each enum variant since I want to retain the default behavior for all other enum variants. Hence, as far as I can tell, these answers are not sufficient for what I am looking for.

The solution I came up with is this:

#[derive(Debug, Eq)]
enum MyEnum {
    Var1,
    Var2(u32),
    Var3(u32, u32),
}

impl PartialEq for MyEnum {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (MyEnum::Var3(a, _), MyEnum::Var3(b, _)) => a == b,
            _ => format!("{:?}", self) == format!("{:?}", other),
        }
    }
}

impl Hash for MyEnum {
    fn hash<H: Hasher>(&self, state: &mut H) {
        match self {
            MyEnum::Var3(a, _) => a.hash(state),
            _ => format!("{:?}", self).hash(state)
        }
    }
}

... and I am looking for feedback/better solutions.

savx2
  • 1,011
  • 2
  • 10
  • 28

2 Answers2

9

The answer by @kmdreko shows how to use a third party crate to implement the Hash trait. In most cases that would be the preferred way because it will require less code and you won't have to manually ensure that the Hash-Eq contract is not violated.

Just for completeness this is how you could have implemented it manually without a third party crate:

impl Hash for MyEnum {
    fn hash<H: Hasher>(&self, state: &mut H) {
        match self {
            MyEnum::Var1 => {
                state.write_u8(1); // use any distinct number as an enum variant identifier
            }

            MyEnum::Var2(v) => {
                state.write_u8(2); // use any distinct number as an enum variant identifier
                v.hash(state); // hash the actual value
            }

            MyEnum::Var3(v, _) => {
                state.write_u8(3);
                v.hash(state);
            }
        }
    }
}

impl PartialEq for MyEnum {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (MyEnum::Var1, MyEnum::Var1) => true,
            (MyEnum::Var2(x), MyEnum::Var2(y)) => x == y,
            (MyEnum::Var3(x, _), MyEnum::Var3(y, _)) => x == y,
            _ => false,
        }
    }
}

impl Eq for MyEnum {}

Svetlin Zarev
  • 14,713
  • 4
  • 53
  • 82
8

The derivative crate has been built to provide automatic implementations for standard traits with support for common customizations. With this crate, you can get exactly what you want and looks like this:

use std::collections::HashSet;
use derivative::Derivative; // 2.2.0

#[derive(Debug, Eq, Derivative)]
#[derivative(PartialEq, Hash)]
enum MyEnum {
    Var1,
    Var2(u32),
    Var3(
        u32, 
        #[derivative(PartialEq="ignore")]
        #[derivative(Hash="ignore")]
        u32
    ),
}

fn main() {
    let mut set = HashSet::new();
    set.insert(MyEnum::Var1);
    set.insert(MyEnum::Var1);
    set.insert(MyEnum::Var2(1));
    set.insert(MyEnum::Var2(2));
    set.insert(MyEnum::Var3(1, 1));
    set.insert(MyEnum::Var3(1, 1));
    set.insert(MyEnum::Var3(2, 1));
    set.insert(MyEnum::Var3(2, 2));
    
    println!("set = {:?}", set);
}
set = {Var1, Var3(1, 1), Var3(2, 1), Var2(2), Var2(1)}

See it on the playground.

kmdreko
  • 42,554
  • 6
  • 57
  • 106