1 unstable release
0.1.0 | Nov 9, 2020 |
---|
#693 in Concurrency
23KB
213 lines
lazy_id
Provides lazy_id::Id
, a thread-safe 64-bit id that only initializes itself to a specific value when you use it rather than when you create it. It works with no_std
(without liballoc
either), is entirely lock-free, currently supports versions as far back as 1.34.0, and has zero dependencies other than libcore.
Usage
use lazy_id::Id;
struct Thing {
id: Id,
// other fields, ...
}
// Now this function can be const, allowing use in statics
const fn new_thing() -> Thing {
Thing { id: Id::lazy(), /* ... */ }
}
static C: Thing = new_thing();
// also works for non-static without meaningful overhead
let a = new_thing();
let b = new_thing();
// `Id` implements `PartialEq`, and many other useful traits.
assert!(a.id != b.id && a.id != C.id);
Why would this be helpful?
If you need a unique per-instance Id
for your type, usually the approach is a global atomic that you increment each time you allocate an id.
The only problem here is that now if you want to store your type in a static
of some sort, you need to use OnceCell
, lazy_static
, or some other lazy initialization crate. These crates are fine and wouldn't be expected to cause perfomance problems the majority of the time, but they can be frustrating if you didn't already need them, and can be extremely undesirable to force this on users of your library.
They also are a dependency you want to avoid if previously you were no_std
— You essentially need to hold a lock while doing generic thread-safe lazy init, and doing that with no_std requires a spinlock, which is... not great (this is why once_cell
requires std for the sync
functionality).
So, why doesn't lazy_id
have this problem? Well, we're not doing any sort of generic initialization. Ours is extremely specific and concrete, and was designed to avoid taking a lock. This means we can easily avoid the std
dep without any spinning.