3 releases
0.1.2 | Sep 8, 2024 |
---|---|
0.1.1 | Sep 8, 2024 |
0.1.0 | Sep 8, 2024 |
#653 in Rust patterns
12KB
201 lines
Simple newtype macro to create strong ID types over other types.
Make a strong ID out of a string:
tacit!(id1::Id1 -> String);
tacit!(id2::Id2 -> Arc<str>);
Try it with ulid
use ulid::Ulid;
tacit!(id::Id -> Ulid);
You can also write a doc comment for your tacits:
tacit!(
/// My unique type!
id::Id -> Ulid
);
Tacit works by generating two structs with the same name. The first part of
the macro, id::Id
generates a struct in a module named id
, like so:
mod id {
#[derive(Clone, Copy, Debug...)]
struct Id;
}
which then uses that type to make a type alias over the Tacit
struct:
pub type Id = Tacit<Ulid, id::Id>;
The first generic argument is the representation, the actual data in the type. The second argument is the identifier, which prevents Tacits of the same representation to not compare with each other or allow them to be passed as arguments to functions expecting different types.
While Tacit
s of different types do not compare, they can convert between other
tacits that implement the same representation:
tacit!(a::A -> Ulid);
tacit!(b::B -> Ulid);
let a: A = Ulid::new().into();
let b: B = a.cast();
the Tacit
struct implements a variety of traits when the representation
implements them. Below is a list of the following:
Clone
Copy
Debug
[1]Display
[1]Eq
PartialEq
Ord
PartialOrd
Hash
From<R>
whereR
is the representation type.FromStr
whereFromStr::Err
isR::FromStr::Err
serde::Serialize
[2]serde::Deserialize
[2]
Note that, if the representation doesn't implement any of these, the Tacit won't have it implemented either. You can still use types that don't implement these, though, as they're implemented conditionally. This enables non-Copy types to be Tacits, while also allowing Copy types to be Copyable.
Additionally, there are special From<str>
/From<String>
impls for Arc<str>
and Rc<str>
as I couldn't find a good way to implements a generic From<R>
for types that encase other types like Arc
does. I figured this was a
reasonable thing to hard-code since I'm essentially hardcoding over Rust's
string literal syntax.
[1] Tacit's display outputs are as follows:
Given tacit!(a::A -> Arc<str>)
:
- Debug:
Tacit(A, "abc")
- Display:
A(abc)
If you want the raw debug/display output without the tacit's name, call:
tacit.repr_debug()
tacit.repr_str()
Note that the automatically generated identifier implements the
tacit::Identity
trait in order to display it's name. If you are not using an
automatically generated ID, ensure your identifier type implements Identity
as well, or else the entire Tacit
cannot use Debug
or Display
. This seems
to be a limitation in Rust's trait system and one I can't find a way to
overcome, as ideally I'd like to simply only print the representation if the
identifier has no display.
[2] A note on serde
: The Tacit will not serialize or deserialize itself,
it is a direct passthrough to the representation type. This means instead of
getting (from tacit!(a::A -> u32)
):
Tacit {
repr: 123
}
You will instead simply get 123
. Keep this in mind when handling
serialization, as this essentially means Tacit will not help you out with
deserializing types.
Dependencies
~0.3–1MB
~21K SLoC