[][src]Struct lazy_id::Id

#[repr(transparent)]pub struct Id(_);

A thread-safe lazily-initialized 64-bit ID.

This is useful if you have a structure which needs a unique ID, but don't want to return it from a const fn, or for callers to be able to use it in statics, without requiring that they lazily initialize the whole thing, e.g. via lazy_static / OnceCell / etc.

The Id type initializes exactly once, and never returns a duplicate — the only Ids which might have the same value as others are ones that come from Id's impl of Clone or Id::from_raw_integer.

It supports most traits you'd want, including Hash, Ord, Clone, Deref<Target = u64>, PartialEq<u64> (and u64 has PartialEq<Id>), Debug, Display, Default (same as Id::new)...

Id's initialization is entirely lock-free and uses only relaxed atomic operations (nor is anything stronger needed). The fast path of Id::get is just a Relaxed atomic load, which is the same cost as a non-atomic load (on platforms that support 64-bit atomics efficiently, anyway).

Example

use lazy_id::Id;
struct Thing {
    id: Id,
    // other fields, ...
}
// Now this function can be const, which can let
// callers avoid needing to use `lazy_static`/`OnceCell`
// for constants of your type
const fn new_thing() -> Thing {
    Thing { id: Id::lazy(), /* ... */ }
}
static C: Thing = new_thing();
let a = new_thing();
let b = new_thing();
assert!(a.id != b.id && a.id != C.id);

FAQs

(Okay, nobody's asked me any of these, but it's a good format for misc. documentation notes).

Are Ids unique?

Across different runs of your program? No. Id generation order is deterministic and occurs in the same order every time.

Within a single run of your program? Yes, with two caveats:

  1. Id implements Clone by producing other Ids with the same numeric value. This seems desirable, as it makes the Id behave as if it had been produced eagerly, and more like a normal number.

  2. The function Id::from_raw_integer forces the creation of an Id with a specific numeric value, which may or may not be a value which has been returned already, and may or may not be one we'll return in the future. This function should be used with care.

It's intentionally okay for unsafe code to assume Ids that it creates through Id::new/Id::lazy/Id::LAZY_INITIALIZER will all have distinct values.

You mentioned a counter, what about overflow?

The counter is 64 bits, so this will realistically never happen. If we assume Id::new takes 1ns (optimistic), this would take 292 years. Attempting to bring this down with ✨The Power Of Fearless Concurrency✨ would probably not change this much (or would make it slower), due to that increasing contention on the counter, but who knows.

If we do overflow, we abort. The abort (and not panic) is because it is global state that is compromised, and so all threads need to be brought down. Additionally, I want lazy_id::Id usable in cases where unsafe code can rely on the values returned being unique (so long as they can ensure that none of them came from Id::from_raw_integer).

What is seq= in the "{:?}" output of an Id?

Id debug formats like "Id(0xhexhexhex; seq=32)". The seq value is a monotonically increasing value that can help identify the order Ids were initialized in, but mostly is a vastly more readable number than the real number, which makes it good for debug output.

I may expose a way to convert between id values and seq values in the future, let me know if you need it.

For a little more explanation: By default, ids are mixed somewhat, which helps discourage people from using them as indexes into arrays or assuming they're sequential, etc (they aren't — they're just monotonic). It also might help them be better hash keys, but with a good hash algo it won't matter.

Implementations

impl Id[src]

pub const fn lazy() -> Self[src]

Create an Id that will be automatically assigned a value when it's needed.

use lazy_id::Id;
struct Thing {
    id: Id,
    // other fields, ...
}
// Now this function can be const, which can let
// callers avoid needing to use `lazy_static`/`OnceCell`
// for constants of your type
const fn new_thing() -> Thing {
    Thing { id: Id::lazy(), /* ... */ }
}
static C: Thing = new_thing();
let a = new_thing();
let b = new_thing();
assert!(a.id != b.id && a.id != C.id);

If you are not in a const context or other situation where you need to use lazy initialization, Id::new is a little more efficient.

If you're in an array literal initializer, Self::LAZY_INITIALIZER may work better for you. Note that using any of these vec! literal will produce a vector with n clones of the same Id, as it invokes clone() — e.g. vec![Id::lazy(); n] should probably be written as, (0..n).map(|_| Id::lazy()).collect::<Vec<_>>(), which will do the right thing (Note that because this isn't const, using Id::new() in the map function would be even better, but isn't the point). This is a problem inherent with vec!, and other types have it as well.

pub fn new() -> Self[src]

Create an Id which has been initialized eagerly.

When you don't need the const, use this, as it is more efficient.

See Id::lazy for the lazy-init version, which is the main selling point of this crate.

Example

let a = Id::new();
let b = Id::new();

assert_ne!(a, b);

pub const LAZY_INITIALIZER: Self[src]

Equivalent to Id::lazy() but usable in situations like static array initializers (or non-static ones too).

For example, the fails because Id isn't Copy, and even if it worked for clone(), it would produce the wrong value.

This example deliberately fails to compile
// Doesn't work :(
static ARR: [Id; 2] = [Id::lazy(); 2];

Using Id::LAZY_INITIALIZER, while awkward, works fine (but only in rust versions above 1.38.0+)

static ARR: [Id; 2] = [Id::LAZY_INITIALIZER; 2];
assert_ne!(ARR[0], ARR[1]);

This API is only present for these sorts of cases, and shouldn't be used when either Id::new or Id::lazy works.

pub fn get(&self) -> u64[src]

Returns the value of this id, lazily initializing if needed.

Often this function does not need to be called explicitly.

Example

let a = Id::lazy();
let b = Id::lazy();

assert_ne!(a.get(), b.get());

pub fn get_nonzero(&self) -> NonZeroU64[src]

Initialized id values are never zero, so we can provide this trivially.

It's unclear how useful it is, although we accept a NonZeroU64 in Id::from_raw_integer, and this makes that easier to call.

Example

let a = Id::new();
let manual_clone_of_a = Id::from_raw_integer(a.get_nonzero());
assert_eq!(a, manual_clone_of_a);

pub const fn from_raw_integer(id: NonZeroU64) -> Self[src]

Create an id with a specific internal value. Something of an escape hatch.

Internally, we reserve 0 as a sentinel that indicates the Id has not been initialized and assigned a value yet. This is why we accept a NonZeroU64 and not a u64. That said, Id::get_nonzero or NonZeroU64::from(id) avoid this being too annoying when the input was from an Id originally

Caveats

This function should be used with care, as it compromises the uniqueness of Id — The resulting Id may be one with a value we use in the future, or have used in the past.

Example

let v = Id::from_raw_integer(NonZeroU64::new(400).unwrap());
assert_eq!(v.get(), 400);

Trait Implementations

impl AsRef<u64> for Id[src]

impl Borrow<u64> for Id[src]

impl Clone for Id[src]

impl Debug for Id[src]

impl Default for Id[src]

impl Deref for Id[src]

type Target = u64

The resulting type after dereferencing.

impl Display for Id[src]

impl Eq for Id[src]

impl<'_> From<&'_ Id> for u64[src]

impl From<Id> for u64[src]

impl From<Id> for NonZeroU64[src]

impl Hash for Id[src]

impl Ord for Id[src]

impl PartialEq<Id> for Id[src]

impl PartialEq<Id> for u64[src]

impl PartialEq<u64> for Id[src]

impl PartialOrd<Id> for Id[src]

Auto Trait Implementations

impl Send for Id

impl Sync for Id

impl Unpin for Id

Blanket Implementations

impl<T> Any for T where
    T: 'static + ?Sized
[src]

impl<T> Borrow<T> for T where
    T: ?Sized
[src]

impl<T> BorrowMut<T> for T where
    T: ?Sized
[src]

impl<T> From<T> for T[src]

impl<T, U> Into<U> for T where
    U: From<T>, 
[src]

impl<T, U> TryFrom<U> for T where
    U: Into<T>, 
[src]

type Error = Infallible

The type returned in the event of a conversion error.

impl<T, U> TryInto<U> for T where
    U: TryFrom<T>, 
[src]

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.