3

I have a User struct:

struct User {
    id: i32,
    email: String,
    // ...
}

In one part of my code I want to get unique users by database ID but in another piece of code I want to get unique users by email address. I have worked on systems before where users got mapped to external system accounts using LDAP CNs, email, etc. and being able to map a user by a different ID in some situations is very useful.

In .NET you can pass in an IEqualityComparer interface to override equals/hash for a particular Dictionary. In C++, the unordered_map class has generic parameters for the hash and eq functions. In Java, I've learned to just use Maps instead of Sets when I want to get unique values keyed off something, but this can be awkward, especially for compound keys.

Truthfully, this is a pretty rare situation and there's always the workaround of using maps instead of sets or creating a wrapper struct with its own Hash/Eq impl block. I am just curious if there's a simpler way to do this in Rust that I am just not aware of yet.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Travis Parks
  • 8,435
  • 12
  • 52
  • 85

1 Answers1

3

Use one or more newtypes with the specific definitions of equality and hashing you want:

use std::hash::{Hash, Hasher};

struct ById(User);

impl Hash for ById {
    fn hash<H>(&self, h: &mut H)
    where
        H: Hasher,
    {
        self.0.id.hash(h)
    }
}

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

impl Eq for ById {}
fn example(k: User, v: i32) {
    let mut h = std::collections::HashMap::new();
    h.insert(ById(k), v);
}

Do I need to use .0 to get at the underlying User when I pull ByIds out of the map?

Yes.

Is there any magic to implicitly convert to the underlying User?

No.

I might be better off implementing Deref or something.

Is it considered a bad practice to implement Deref for newtypes?

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Do I need to use `.0` to get at the underlying `User` when I pull `ById`s out of the map? Is there any magic to implicitly convert to the underlying `User`? Although, I could see that being too magical. I might be better off implementing `Deref` or something. Thoughts? – Travis Parks Jan 11 '21 at 17:50
  • 1
    I've replied inline. – Shepmaster Jan 11 '21 at 17:53