Expand description
§Identify
The identify
crate provides strongly-typed UUIDs,
statically associated with other types.
This can simplify architecture and implementation,
while enhancing type safety and referential integrity,
in systems where data sets are being manipulated
asynchronously across multiple threads and/or physical locations.
§Basic identification
You can use the Identifier
type to reference any other data type,
including those from 3rd-party crates, those built into Rust,
and those that you define yourself:
use identify::Identifier;
/// An ID for a type from a 3rd-party crate.
type BuilderID = Identifier<uuid::Builder>;
/// An ID for a Rust primitive.
type FloatID = Identifier<f32>;
/// A simple struct.
struct Foo;
/// An ID for a local type:
type FooID = Identifier<Foo>;
/// A randomly-generated UUID, bound to the `Foo` type.
let id = FooID::new();
§The value of UUIDs
Unless you are building systems where failure could result in physical injury or death, you can generally assume that each Universally Unique Identifier (UUID) is unique within the entirety of the known universe.
According to computer science experts, if your platform supplies a reasonable entropy pool, and the algorithms within the uuid crate are implemented correctly, it is far more likely that your software will fail due to catastrophic natural disasters or other external factors beyond your control, than that a meaningful collision between two UUIDs will ever occur.
Please feel encouraged to do your own research, if you lack confidence in the reliability of UUIDs, because the usefulness of this entire library rests upon it. :)
§A simple identifiable type
For the examples below, let’s establish the following simple type:
use identify::Identifier;
/// A basic "identifiable" type.
#[derive(Debug)]
struct Foo {
/// The UUID of this Foo.
id: Identifier<Self>,
// ...other fields
}
/// Uniquely identifies a specific Foo.
type FooID = Identifier<Foo>;
impl Foo {
/// Constructs a Foo with a random ID.
pub fn new() -> Self {
Self {
id: FooID::new(),
// ...initialize other fields
}
}
}
// This implementation constructs a Foo from the specified pre-existing ID.
impl From<FooID> for Foo {
fn from(id: FooID) -> Self {
Self {
id,
// ...initialize other fields
}
}
}
§Providing infallible identity
The Identify
trait allows a type’s UUID to be queried:
use identify::Identify;
impl Identify for Foo {
fn id(&self) -> Identifier<Self> {
self.id
}
}
let foo_id = FooID::new();
let foo = Foo::from(foo_id);
assert_eq!(foo.id(), foo_id);
§Multiple identities
If one type holds an Identifier
for another,
you might want to provide an implementation of Identify
for that relationship. To illustrate this,
let’s establish a “helper” type, which has its own identifier,
but which also carries an identifier for the Foo
type:
#[derive(Debug)]
/// A simple "helper" for the Foo type.
struct FooHelper {
/// Our own UUID.
id: Identifier<Self>,
/// The UUID of the Foo which we are "helping".
foo_id: FooID,
// ...other fields
}
impl FooHelper {
/// Constructs a "helper" for the indicated Foo.
pub fn new(foo: &Foo) -> Self {
Self {
id: Identifier::new(), // <- generate our own UUID
foo_id: foo.id(), // <- store the UUID of our Foo
// ...initialize other fields
}
}
}
Now, we can write both Self
and Foo
implementations of Identify
for FooHelper
:
impl Identify for FooHelper {
fn id(&self) -> Identifier<Self> {
self.id
}
}
impl Identify<Foo> for FooHelper {
fn id(&self) -> Identifier<Foo> {
self.foo_id
}
}
We can then supply FooHelper
in places where we need something which can be identified as a Foo
:
let foo = Foo::new();
let helper = FooHelper::new(&foo);
/// Returns `true` if both inputs identify as the same Foo.
fn same_foo(thing1: impl Identify<Foo>, thing2: impl Identify<Foo>) -> bool {
thing1.id() == thing2.id()
}
assert!(same_foo(foo, helper));
§Serialization and deserialization
Full support for serde may be implemented in future,
but an Identifier
can currently be serialized or deserialized as a u128:
let id_code: u128 = 0xF548A8E03C7DE339C7A3BAF7874AE524;
let id = FooID::from(id_code);
assert_eq!(id.as_u128(), id_code);
You can also serialize an Identifier
via the ToString blanket implementation on Display:
assert_eq!(id.to_string(), "f548a8e0-3c7d-e339-c7a3-baf7874ae524");
§Casting
In rare cases where you may need to use the numeric value for one type of Identifier
as the numeric value for another, you can perform this conversion via the Identifier::cast
function:
#[derive(Debug)]
struct Bar;
type BarID = Identifier<Bar>;
let foo = FooID::new();
let bar: BarID = foo.cast();
assert_eq!(foo.as_u128(), bar.as_u128());
§In conclusion
If you find this library useful, and you’re curious to know what else I might be building or doing, you might enjoy visiting my website, where you’ll find content dating back all the way to the late 90s. Cheers!
Structs§
- Identifier
- A Uuid identifier for an instance of generic type
T
.
Traits§
- Identify
- A trait for indicating that each instance of a type holds a unique Identifier.