Rust references don't implement Eq/Hash? How to use them as hash map key?
Asked Answered
F

2

6

I want to build a hashmap where they keys are references. I want that equality for those references means reference equality, i.e. both references borrow the same object.

use std::collections::hash_map::HashMap;

struct SomeKey();
struct SomeValue();

fn main() {
    let m = HashMap::<&SomeKey, SomeValue>::new();
    
    let t = SomeKey();
    m.get(&t);
}

Unfortunately, this fails and the compiler tells me that &SomeKey doesn't implement Hash/Eq.

error[E0599]: the method `get` exists for struct `HashMap<&SomeKey, SomeValue>`, but its trait bounds were not satisfied
  --> src/main.rs:10:7
   |
10 |     m.get(&t);
   |       ^^^ method cannot be called on `HashMap<&SomeKey, SomeValue>` due to unsatisfied trait bounds
   |
   = note: the following trait bounds were not satisfied:
           `&SomeKey: Eq`
           `&SomeKey: Hash`

(Playground)

I noticed that if i implement Eq+Hash for SomeKey, then it works, but that'll likely use the underlying object equality then which is not what I want.

Is there a way I can use references as hash map keys based on pointer equality?

Freehearted answered 24/9, 2021 at 1:31 Comment(1)
tips: youtube.com/watch?v=CV5CjUlcqswQuarterphase
L
6

You could use the by_address crate. It wraps any pointer/reference type to compare objects by address instead of by contents.

use std::collections::hash_map::HashMap;

use by_address::ByAddress;

struct SomeKey();
struct SomeValue();

fn main() {
    let mut m = HashMap::<ByAddress<&SomeKey>, SomeValue>::new();

    let t1 = SomeKey();
    let t2 = SomeKey();

    m.insert(ByAddress(&t1), SomeValue());
    assert!(m.get(&ByAddress(&t1)).is_some());
    assert!(m.get(&ByAddress(&t2)).is_none());
}
Lexicology answered 24/9, 2021 at 1:45 Comment(0)
H
4

This can be handled by implementing Hash and Eq on the references and then using the functions from std::ptr to perform the operations. Casting the references to a usize and then operating on those can also work. You just have to make sure to dereference once in the impls because &Self has a type of &&SomeKey.

use std::collections::HashMap;

struct SomeKey();
struct SomeValue();
impl<'a> PartialEq for &'a SomeKey{
    fn eq(&self, other:&Self) -> bool{
        std::ptr::eq(*self, *other)
    }
}
impl<'a> Eq for &'a SomeKey{}
use std::hash::Hash;
use std::hash::Hasher;
impl<'a> Hash for &'a SomeKey {
    fn hash<H: Hasher>(&self, state: &mut H) {
        std::ptr::hash(*self, state)
    }
}


fn main() {
    let m = HashMap::<&SomeKey, SomeValue>::new();
    
    let t = SomeKey();
    m.get(&&t);
}

Playground.

Heywood answered 24/9, 2021 at 3:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.