0

I want to get an ability to create hashset of an enum. As I understand, all I need to do, is to implement PartialEq, Eq and Hash traits for my enums. But the problem is that the enum is declared in some other crate. And because of that implemenation is impossilbe. So, I decided to create a struct, which contains a key from the enum, and make it hashable instead.

use rdev::{Key}; // needed enum

pub struct MyKey(Key); // my implementation which supposed to be hashable

All I need is to implement the traits, but there's a problem

impl Hash for MyKey {
    fn hash<H: Hasher>(&self, state: &mut H) {
        state.write_u16(self.0 as u16);                                                                                                                                                                    
//                      ^^^^^^^^^^^^^ an `as` expression can only be used to convert between primitive types or to coerce to a specific trait object  
    }
}

impl PartialEq for MyKey {
    fn eq(&self, other: &Self) -> bool {
        self.0 == other.0
    }
}

impl Eq for MyKey {}

I can't get the point of this warning. If I declare some other enum, as u16 will work just fine. But with outer enum it does not.

Could you, please, explain why? Or maybe you can give me more beautiful way to get an ability for creating HashSet of enum's keys.

Yoskutik
  • 1,859
  • 2
  • 17
  • 43
  • 3
    You can only do numeric conversions like this when none of the enum's variants contains data. This enum has a `Unknown(u32)` variant, which contains data, so the conversion to a numeric data type cannot make sense for all possible values. – cdhowie Jul 10 '22 at 14:13
  • @cdhowie although it would be enough for a hash function (not a very good one, but a working one I guess?) – jthulhu Jul 10 '22 at 14:24
  • 4
    You can use [`std::mem::discriminant()`](https://doc.rust-lang.org/stable/std/mem/fn.discriminant.html) instead of `as u16` to hash the unit-like variants, and then `match` to hash the inner data of any non unit-like. – rodrigo Jul 10 '22 at 14:30
  • From the official documentation of `rdev`: *"This crate is so far a pet project for me to understand the rust ecosystem."* I'm not sure if you should use this crate in an actual project. If you do, understand that this is a fairly new crate, so if you encounter problems like that, don't try to work around them but instead open an issue at their codebase or even provide a pullrequest. – Finomnis Jul 11 '22 at 08:55

1 Answers1

2

As @rodrigo mentioned, it's probably more useful to use std::mem::discriminant() to distinguish between Enum values.

use std::{
    collections::HashSet,
    fmt::Debug,
    hash::{Hash, Hasher},
};

#[derive(PartialEq)]
pub struct MyKey(rdev::Key);

impl Eq for MyKey {}

impl Debug for MyKey {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}

impl Hash for MyKey {
    fn hash<H: Hasher>(&self, state: &mut H) {
        std::mem::discriminant(&self.0).hash(state);

        if let rdev::Key::Unknown(val) = &self.0 {
            val.hash(state)
        }
    }
}

fn main() {
    use rdev::{listen, EventType};

    let mut keys: HashSet<MyKey> = HashSet::new();

    listen(move |event| {
        let changed = match event.event_type {
            EventType::KeyPress(key) => keys.insert(MyKey(key)),
            EventType::KeyRelease(key) => keys.remove(&MyKey(key)),
            _ => false,
        };
        if changed {
            println!("Keys: {:?}", keys);
        }
    })
    .unwrap();
}

That said, there is already a PR to add this directly to rdev, but it didn't get any response sind a couple of months.

Further, the crate states:

This crate is so far a pet project for me to understand the rust ecosystem.

All in all, unless there is an update at some point, I'd consider this crate abandoned and use something else instead.

Finomnis
  • 18,094
  • 1
  • 20
  • 27