The idea is to make a deadlock-free version of the Once type that is included in the standard library. Unlike with mutexes and rwlocks, it shouldn't be too difficult to create this as a wrapper around the Once that's already included in the standard library, but let me know if you run into any problems. Jury's still out on whether this should be exported or not. We could keep this type as an implementation detail, and just export OnceLock and LazyLock.
The API might look something like the following:
impl Once {
pub const fn new() -> Once;
// I decided against having a separate `call_once_force`. This will be able to handle
// poisoning or not handle it. That's completely up to the user.
pub fn call_once(&self, key: impl Keyable, f: impl FnOnce(&OnceState));
// This will allow us to lock a collection and initialize a `Once` at the same time. The
// rule is that the `Once` must begin its initialization after[^1] anything else is locked.
// [^1]: Or before. You pick.
pub fn call_and_scoped_lock<L: RawLock + Lockable>(&self, key: impl Keyable, lock: &L, f: impl FnOnce(&OnceState, L::DataMut));
// I don't like these name and I'm open to suggestions
pub fn call_and_scoped_read<L: RawLock + Sharable>(&self, key: impl Keyable, lock: &L, f: impl FnOnce(&OnceState, L::DataRef));
// I'm still thinking about those last two methods. I could imagine a system where you
// can keep the created guards after `call_once`, where the tradeoff is you have to pass
// in an owned `ThreadKey` rather than a Keyable. This will also need to be rethought when
// locking iterators are introduced.
// pub fn call_and_lock<'g, L: RawLock + Lockable>(
// &self,
// key: impl Keyable,
// lock: &'g L,
// f: impl FnOnce(&OnceState, L::DataMut)
// ) -> LockGuard<L::Guard<'g>>
// Another idea is to allow calling `call_once` without a `ThreadKey`, by instead passing in
// a guard that holds a `ThreadKey`. This would require a new trait, implemented for
// `LockGuard`, `PoisonGuard`, `MutexGuard`, `RwLockWriteGuard`, and `RwLockReadGuard`.
// This also would require that Onces are always locked after any other locks
// pub fn call_with_lock<G: GuardWithThreadKey>(
// &self,
// guard: GuardWithThreadKey,
// f: impl FnOnce(&OnceState, L::DataMut)
// ) -> LockGuard<L::Guard<'g>>
// This doesn't require a thread key, because it's impossible for `f` to contain a second call
// to this `Once`
pub fn call_once_mut(&mut self, f: impl FnOnce(&OnceState));
pub fn is_completed(&self) -> bool;
// The `wait` and `wait_force` methods can't be implemented, since they would
// require nightly Rust. It would work if we implemented `Once` ourselves using
// the OS APIs, but let's not do that right now.
The idea is to make a deadlock-free version of the
Oncetype that is included in the standard library. Unlike with mutexes and rwlocks, it shouldn't be too difficult to create this as a wrapper around theOncethat's already included in the standard library, but let me know if you run into any problems. Jury's still out on whether this should be exported or not. We could keep this type as an implementation detail, and just exportOnceLockandLazyLock.The API might look something like the following: