Hello,
I want to use the uuid crate for multiple structs (like User
, UserGroup
...).
Main goal: Even if it's not likely to happen, I want to ensure at compile time that an uuid from the struct User
cannot be compared with an uuid from the struct UserGroup
.
So the idea is to create a generic type Id<T>
, which will trigger an error when trying to compare Id<User>
with Id<UserGroup>
.
My first try was simply:
struct Id<T> {
uuid: Uuid,
}
But I corrected it by adding PhantomData
:
struct Id<T> {
uuid: Uuid,
_1: PhantomData<T>,
}
In order to use it, I also added some derives:
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
struct Id<T> {
uuid: Uuid,
_1: PhantomData<T>,
}
But then I got this error when trying to use Id<User>
as a HashMap
key:
error[E0599]: the method `insert` exists for struct `HashMap<Id<User>, User>`, but its trait bounds were not satisfied
--> src/main.rs:41:11
|
10 | pub struct Id<T> {
| ----------------
| |
| doesn't satisfy `Id<User>: Eq`
| doesn't satisfy `Id<User>: Hash`
...
41 | users.insert(user.id, user);
| ^^^^^^
|
= note: the following trait bounds were not satisfied:
`Id<User>: Eq`
`Id<User>: Hash`
Here is the whole code:
use std::{
collections::HashMap,
hash::{Hash},
marker::PhantomData,
};
use uuid::Uuid;
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct Id<T> {
pub uuid: Uuid,
_1: PhantomData<T>,
}
impl<T> Id<T> {
fn new() -> Self {
Self {
uuid: Uuid::new_v4(),
_1: PhantomData,
}
}
}
pub struct User {
pub id: Id<User>,
pub username: String,
}
impl User {
pub fn new(username: impl Into<String>) -> Self {
Self {
id: Id::new(),
username: username.into(),
}
}
}
fn main() {
let mut users: HashMap<Id<User>, User> = HashMap::new();
let user = User::new("bob");
users.insert(user.id, user);
}
The only way I've found to solve the error is to implement manually every trait I need for Id<T>
, instead of using the #[derive]
attribute:
use std::{
collections::HashMap,
hash::{Hash, Hasher},
marker::PhantomData,
};
use uuid::Uuid;
pub struct Id<T> {
pub uuid: Uuid,
_1: PhantomData<T>,
}
impl<T> Clone for Id<T> {
fn clone(&self) -> Id<T> {
Id {
uuid: self.uuid,
_1: PhantomData,
}
}
}
impl<T> Copy for Id<T> {}
impl<T> PartialEq for Id<T> {
fn eq(&self, other: &Id<T>) -> bool {
self.uuid == other.uuid
}
}
impl<T> Eq for Id<T> {}
impl<T> Hash for Id<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.uuid.hash(state);
}
}
impl<T> Id<T> {
fn new() -> Self {
Self {
uuid: Uuid::new_v4(),
_1: PhantomData,
}
}
}
pub struct User {
pub id: Id<User>,
pub username: String,
}
impl User {
pub fn new(username: impl Into<String>) -> Self {
Self {
id: Id::new(),
username: username.into(),
}
}
}
fn main() {
let mut users: HashMap<Id<User>, User> = HashMap::new();
let user = User::new("bob");
users.insert(user.id, user);
}
Is it possible to avoid implementing each basic trait in this situation? Or is there a better way to implement this behaviour (incompatible uuids at compile time) ?