Another Rust macro use case: concise newtypes
A footgun I hit recently in my current Rust project is if I have a few arguments of the same type for a function, I sometimes can mix up the order.
pub fn mint_session(
user_id: uuid::Uuid,
org_id: uuid::Uuid,
) -> Session {
...
}
let session = mint_session(org_id, user_id); // compiles
There’s no keyword arguments in Rust, and creating a struct for each of these functions seems unnecessarily verbose.
I could just create a type alias, but that actually doesn’t provide any type safety!
type UserId = uuid::Uuid;
type OrgId = uuid::Uuid;
pub fn mint_session(
user_id: UserId,
org_id: OrgId,
) -> Session {
...
}
let session = mint_session(org_id, user_id); // compiles!
The proper way to solve this is to actually use the newtype idiom:
pub struct UserId(uuid::Uuid);
pub struct OrgId(uuid::Uuid);
pub fn mint_session(
user_id: UserId,
org_id: OrgId,
) -> Session {
...
}
let session = mint_session(org_id, user_id); // doesn’t compile!
But when using this idiom, especially when using these types to
interface with sqlx and serde, defining them
can be pretty verbose and resulted in a lot of repeated code across my
project, so I instead opted to create this macro:
macro_rules! typed_id {
($name:ident) => {
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Hash,
serde::Deserialize,
serde::Serialize,
sqlx::Type,
)]
#[sqlx(transparent)]
pub struct $name(pub uuid::Uuid);
impl From<uuid::Uuid> for $name {
fn from(value: uuid::Uuid) -> Self {
Self(value)
}
}
impl From<$name> for uuid::Uuid {
fn from(value: $name) -> Self {
value.0
}
}
};
}
And now I can just create these handy newtypes very concisely without having to unpack tuples or deconstruct when having to extract the underlying value:
typed_id!(UserId);
typed_id!(OrgId);