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.
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.
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 {}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With