[Feedback] A wrapper for object in HashSets that contains their key

Hello all !

I recently stumbled upon the issue of having a HashMap where the key would also be contained in the object stored. I saw this as an issue because most of my keys were String and since I could not use a reference &str on the object's String to act as a key in the HashMap, I needed to reallocate another String.

Then I saw this topic on SO that gave me the inspiration to replace the HashMap with a HashSet and create my own generic wrapper around object inside that allow me to easily use get() on the HashSet.

Since this is pretty much the first time I do some meta-programming in Rust, I wanted some feedback on the quality of my code, and in particular on the use of trait bounds and PhantomData.

use std::borrow::Borrow;
use std::cmp::{Eq, Ord, Ordering, PartialEq};
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use std::ops::Deref;
use std::rc::Rc;

pub trait WithId<K>
where
    K: Hash + PartialEq + Eq + Ord + ?Sized,
{
    fn id(&self) -> &K;
}

#[derive(Debug)]
pub struct HashedNode<T, K>
where
    K: Hash + PartialEq + Eq + Ord + ?Sized,
    T: WithId<K>,
{
    data: Rc<T>,
    phantom: PhantomData<K>,
}

impl<T, K> HashedNode<T, K>
where
    K: Hash + PartialEq + Eq + Ord + ?Sized,
    T: WithId<K>,
{
    pub fn from_value(value: T) -> Self {
        HashedNode {
            data: Rc::new(value),
            phantom: PhantomData,
        }
    }
}

impl<T, K> PartialEq for HashedNode<T, K>
where
    K: Hash + PartialEq + Eq + Ord + ?Sized,
    T: WithId<K>,
{
    fn eq(&self, other: &Self) -> bool {
        return self.data.id().eq(&other.data.id());
    }
}
impl<T, K> PartialOrd for HashedNode<T, K>
where
    K: Hash + PartialEq + Eq + Ord + ?Sized,
    T: WithId<K>,
{
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        return self.data.id().partial_cmp(&other.data.id());
    }
}
impl<T, K> Eq for HashedNode<T, K>
where
    K: Hash + PartialEq + Eq + Ord + ?Sized,
    T: WithId<K>,
{
}
impl<T, K> Ord for HashedNode<T, K>
where
    K: Hash + PartialEq + Eq + Ord + ?Sized,
    T: WithId<K>,
{
    fn cmp(&self, other: &Self) -> Ordering {
        return self.data.id().cmp(&other.data.id());
    }
}
impl<T, K> Borrow<K> for HashedNode<T, K>
where
    K: Hash + PartialEq + Eq + Ord + ?Sized,
    T: WithId<K>,
{
    fn borrow(&self) -> &K {
        return &self.data.id();
    }
}

impl<T, K> Hash for HashedNode<T, K>
where
    K: Hash + PartialEq + Eq + Ord + ?Sized,
    T: WithId<K>,
{
    fn hash<G: Hasher>(&self, state: &mut G) {
        self.data.id().hash(state);
    }
}

impl<T, K> Deref for HashedNode<T, K>
where
    K: Hash + PartialEq + Eq + Ord + ?Sized,
    T: WithId<K>,
{
    type Target = Rc<T>;

    fn deref(&self) -> &Self::Target {
        &self.data
    }
}

That way I can do this:

#[cfg(test)]
mod test {
    use super::*;

    #[derive(Debug)]
    pub struct Foo {
        pub id: String,
        pub value: usize,
    }

    impl WithId<str> for Foo {
        fn id(&self) -> &str {
            &self.id
        }
    }

    #[test]
    fn test() {
        use std::collections::HashSet;
        let f = Foo {
            id: "a".to_owned(),
            value: 2,
        };
        let mut h: HashSet<HashedNode<Foo, str>> = HashSet::new();
        h.insert(HashedNode::from_value(f));
        let f2: &Rc<Foo> = &h.get("a").unwrap().data;
        assert_eq!(f2.value, 2);
    }
}

As of now, I am not super happy with the need to call .data after the .get(), but since I used Deref to access the key, I cannot reimplement it to access the Rc wrapped data underneath.

If you have any advices or your think something is wrong, I'll happily take comments :slight_smile:

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.