Accessing a non-u32 as a u32

I have something that looks something like this:

struct Id {
  id: u32
}

impl Hash for Id {
  fn hash<H>(&self, state: &mut H)
  where
    H: Hasher
  {
    self.id.hash(state);
  }
}

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

There's obviously more to it than that (in particular there's a Drop implementation for Id), but for the purposes of my question it's a proxy around a u32.

It's used like this:

let vconns: HashMap<Arc<Id>, Connection> = HashMap::new();

A situation has manifested itself where an application has access to a raw u32 (that is guaranteed to exist in the form of an Id in the HashMap). Assuming there's no way to create an Id from a u32, is there some way to:

  • Keep the HashMap's key an Arc<Id>
  • Allow a Connection to be looked up using an u32
  • Avoid introducing a second HashMap (to map u32's to Id's)

.. where the assumption is the hash of Arc<Id> will always be a proxy for an internal actual u32.

I'm using hashbrown's HashMap, and I had some vague memory of having used it to looked up items using manually calculated hash values, which would sort of fit here -- but I can't find how I could have done that, so I'm wondering if that was just a pizza dream.

Almost, but not quite. If your key type implements Borrow<U>, then HashMap will let you look things up using values of type &U. Unfortunately, Arc's Borrow implementation isn't transitive: As things stand right now, you can look only things up by &Arc<Id> or &Id. You also can't implement Borrow<u32> for Arc<Id> due to orphan rules.

You can, however, wrap Arc<Id> into a newtype and implement Borrow<u32> for that.

Alternatively, you can make a newtype for raw ids that doesn't have the drop machinery:

#[derive(Hash,Eq,PartialEq)]
struct RawId(u32);

#[derive(Hash,Eq,PartialEq)]
struct Id(RawId);

impl Borrow<RawId> for Id {
    fn borrow(&self)->&RawId { &self.0 }
}

impl Borrow<RawId> for Arc<Id> {
    fn borrow(&self)->&RawId { &self.0 }
}

// vconns.get(&RawId( external_id )) should work now
3 Likes

Just out of curiosity: Is this a due to limitations in the language or is it purely an Arc implementation limitation?

It's a coherence thing -- a transitive implementation conflicts with the reflective one.

3 Likes

It's been a long time since I saw a sentence containing both "transitive" and "reflective" that made so much sense. :grin:

2 Likes

This is the first time that I ever heard these concepts applied to trait implementations. Any chance that you could post some literature to further dive / understand them?

The reflective impl is applying the trait to Arc<T> itself (itself, hence reflective).

The transitive impl would be something like

impl<T, U> Borrow<T> for Arc<U>
where
    U: Borrow<T>

chaining the two Borrows together, threading it through the inner type of the Arc (hence, transitive).

(This fails due to coherence when U = T, since T: Borrow<T>.)

4 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.