Pointer types in Rust, such as Box<T>
, Rc<T>
and Arc<T>
, implement their Eq
and Hash
instance in a deep way, i.e. they call to the respective instances of T
. This is different from what a C/C++ programmer would expect from pointer types, which compare always shallowly in those languages.
In some cases one may want to still compare shallowly two pointer types. Since implementing Eq
and Hash
for Rc<MyType>
is not possible because it would be an orphan instance, then I figured out I may do it by implement the shallow comparison and hashing in the type's instance, as follows:
pub struct MyType {
pub x: (),
}
impl PartialEq for MyType {
fn eq(&self, other: &Self) -> bool {
std::ptr::eq(self, other)
}
}
impl Eq for MyType {}
impl Hash for MyType {
fn hash<H: Hasher>(&self, state: &mut H) {
(self as *const MyType).hash(state);
}
}
In this way, Box<MyType>
, Rc<MyType>
, and Arc<MyType>
would compare shallowly (and MyType
directly as well).
However, I'm quite new to Rust so I'm not sure this approach is sensible.
true
when and only when I compare exactly the same object?Rust generally eschews the idea of equality by identity. In particular, due to the interaction between how identity is determined in other languages (pointer address) and Rust's implicit moves, this can cause the identity of values to change as they are moved around, which is typically not desired.
As you correctly point out, storing a value in a heap allocation "locks" the pointer-based identity of the value. Well, mostly – you can still move out of a Box<T>
, most other smart pointers provide a into_inner
of some sort.
Because of this, there are two ways you can approach this problem:
MyTypeImpl
as the "real" implementation, and a struct MyType(Pin<Box<MyType>>)
on which you can implement hashing and equality).AtomicUsize
from which you can dispense unique object identifiers. This does increase the size of each value and potentially impacts visibility (the identifier should be private so it cannot be tampered with).use std::{
hash::{Hash, Hasher},
sync::atomic::AtomicUsize,
};
pub struct MyType {
id: usize,
pub x: (),
}
impl MyType {
pub fn new(x: ()) -> Self {
static ID: AtomicUsize = AtomicUsize::new(0);
Self {
id: ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
x,
}
}
}
impl PartialEq for MyType {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for MyType {}
impl Hash for MyType {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}