[PATCH v14 1/7] rust: sync: add `OnceLock`

Andreas Hindborg posted 7 patches 3 months, 1 week ago
There is a newer version of this series
[PATCH v14 1/7] rust: sync: add `OnceLock`
Posted by Andreas Hindborg 3 months, 1 week ago
Introduce the `OnceLock` type, a container that can only be written once.
The container uses an internal atomic to synchronize writes to the internal
value.

Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
---
 rust/kernel/sync.rs           |   1 +
 rust/kernel/sync/once_lock.rs | 104 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 105 insertions(+)

diff --git a/rust/kernel/sync.rs b/rust/kernel/sync.rs
index c7c0e552bafe..f2ee07315091 100644
--- a/rust/kernel/sync.rs
+++ b/rust/kernel/sync.rs
@@ -15,6 +15,7 @@
 mod condvar;
 pub mod lock;
 mod locked_by;
+pub mod once_lock;
 pub mod poll;
 pub mod rcu;
 
diff --git a/rust/kernel/sync/once_lock.rs b/rust/kernel/sync/once_lock.rs
new file mode 100644
index 000000000000..cd311bea3919
--- /dev/null
+++ b/rust/kernel/sync/once_lock.rs
@@ -0,0 +1,104 @@
+//! A container that can be initialized at most once.
+
+use super::atomic::ordering::Acquire;
+use super::atomic::ordering::Release;
+use super::atomic::Atomic;
+use kernel::types::Opaque;
+
+/// A container that can be populated at most once. Thread safe.
+///
+/// Once the a [`OnceLock`] is populated, it remains populated by the same object for the
+/// lifetime `Self`.
+///
+/// # Invariants
+///
+/// `init` tracks the state of the container:
+///
+/// - If the container is empty, `init` is `0`.
+/// - If the container is mutably accessed, `init` is `1`.
+/// - If the container is populated and ready for shared access, `init` is `2`.
+///
+/// # Example
+///
+/// ```
+/// # use kernel::sync::once_lock::OnceLock;
+/// let value = OnceLock::new();
+/// assert_eq!(None, value.as_ref());
+///
+/// let status = value.populate(42u8);
+/// assert_eq!(true, status);
+/// assert_eq!(Some(&42u8), value.as_ref());
+/// assert_eq!(Some(42u8), value.copy());
+///
+/// let status = value.populate(101u8);
+/// assert_eq!(false, status);
+/// assert_eq!(Some(&42u8), value.as_ref());
+/// assert_eq!(Some(42u8), value.copy());
+/// ```
+pub struct OnceLock<T> {
+    init: Atomic<u32>,
+    value: Opaque<T>,
+}
+
+impl<T> Default for OnceLock<T> {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl<T> OnceLock<T> {
+    /// Create a new [`OnceLock`].
+    ///
+    /// The returned instance will be empty.
+    pub const fn new() -> Self {
+        // INVARIANT: The container is empty and we set `init` to `0`.
+        Self {
+            value: Opaque::uninit(),
+            init: Atomic::new(0),
+        }
+    }
+
+    /// Get a reference to the contained object.
+    ///
+    /// Returns [`None`] if this [`OnceLock`] is empty.
+    pub fn as_ref(&self) -> Option<&T> {
+        if self.init.load(Acquire) == 2 {
+            // SAFETY: As determined by the load above, the object is ready for shared access.
+            Some(unsafe { &*self.value.get() })
+        } else {
+            None
+        }
+    }
+
+    /// Populate the [`OnceLock`].
+    ///
+    /// Returns `true` if the [`OnceLock`] was successfully populated.
+    pub fn populate(&self, value: T) -> bool {
+        // INVARIANT: We obtain exclusive access to the contained allocation and write 1 to
+        // `init`.
+        if let Ok(0) = self.init.cmpxchg(0, 1, Acquire) {
+            // SAFETY: We obtained exclusive access to the contained object.
+            unsafe { core::ptr::write(self.value.get(), value) };
+            // INVARIANT: We release our exclusive access and transition the object to shared
+            // access.
+            self.init.store(2, Release);
+            true
+        } else {
+            false
+        }
+    }
+}
+
+impl<T: Copy> OnceLock<T> {
+    /// Get a copy of the contained object.
+    ///
+    /// Returns [`None`] if the [`OnceLock`] is empty.
+    pub fn copy(&self) -> Option<T> {
+        if self.init.load(Acquire) == 2 {
+            // SAFETY: As determined by the load above, the object is ready for shared access.
+            Some(unsafe { *self.value.get() })
+        } else {
+            None
+        }
+    }
+}

-- 
2.47.2
Re: [PATCH v14 1/7] rust: sync: add `OnceLock`
Posted by Wren Turkal 3 months ago
On 7/2/25 6:18 AM, Andreas Hindborg wrote:
> Introduce the `OnceLock` type, a container that can only be written once.
> The container uses an internal atomic to synchronize writes to the internal
> value.
> 
> Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
> ---
>   rust/kernel/sync.rs           |   1 +
>   rust/kernel/sync/once_lock.rs | 104 ++++++++++++++++++++++++++++++++++++++++++
>   2 files changed, 105 insertions(+)
> 
> diff --git a/rust/kernel/sync.rs b/rust/kernel/sync.rs
> index c7c0e552bafe..f2ee07315091 100644
> --- a/rust/kernel/sync.rs
> +++ b/rust/kernel/sync.rs
> @@ -15,6 +15,7 @@
>   mod condvar;
>   pub mod lock;
>   mod locked_by;
> +pub mod once_lock;
>   pub mod poll;
>   pub mod rcu;
>   
> diff --git a/rust/kernel/sync/once_lock.rs b/rust/kernel/sync/once_lock.rs
> new file mode 100644
> index 000000000000..cd311bea3919
> --- /dev/null
> +++ b/rust/kernel/sync/once_lock.rs
> @@ -0,0 +1,104 @@
> +//! A container that can be initialized at most once.
> +
> +use super::atomic::ordering::Acquire;
> +use super::atomic::ordering::Release;
> +use super::atomic::Atomic;
> +use kernel::types::Opaque;
> +
> +/// A container that can be populated at most once. Thread safe.
> +///
> +/// Once the a [`OnceLock`] is populated, it remains populated by the same object for the
> +/// lifetime `Self`.
> +///
> +/// # Invariants
> +///
> +/// `init` tracks the state of the container:
> +///
> +/// - If the container is empty, `init` is `0`.
> +/// - If the container is mutably accessed, `init` is `1`.
> +/// - If the container is populated and ready for shared access, `init` is `2`.
> +///
> +/// # Example
> +///
> +/// ```
> +/// # use kernel::sync::once_lock::OnceLock;
> +/// let value = OnceLock::new();
> +/// assert_eq!(None, value.as_ref());
> +///
> +/// let status = value.populate(42u8);
> +/// assert_eq!(true, status);
> +/// assert_eq!(Some(&42u8), value.as_ref());
> +/// assert_eq!(Some(42u8), value.copy());
> +///
> +/// let status = value.populate(101u8);
> +/// assert_eq!(false, status);
> +/// assert_eq!(Some(&42u8), value.as_ref());
> +/// assert_eq!(Some(42u8), value.copy());
> +/// ```
> +pub struct OnceLock<T> {
> +    init: Atomic<u32>,
> +    value: Opaque<T>,
> +}

This type looks very much like the Once type in rust's stdlib. I am 
wondering if the api could be changed to match that api. I know that 
this type is trying to provide a version subset of std::sync::OnceLock 
that doesn't allow resetting the type like these apis:

* https://doc.rust-lang.org/std/sync/struct.OnceLock.html#method.get_mut
* https://doc.rust-lang.org/std/sync/struct.OnceLock.html#method.take

However, these methods can only be used on mut. See here for failing 
example: 
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=a78e51203c5b9555e3c151e162f0acab

I think it might make more sense to match the api of the stdlib API and 
maybe only implement the methods you need.

> +
> +impl<T> Default for OnceLock<T> {
> +    fn default() -> Self {
> +        Self::new()
> +    }
> +}

Any reason not to use #[derive(Default)]?
> +
> +impl<T> OnceLock<T> {
> +    /// Create a new [`OnceLock`].
> +    ///
> +    /// The returned instance will be empty.
> +    pub const fn new() -> Self {

Like new in std OnceLock. Matches. Good.

> +        // INVARIANT: The container is empty and we set `init` to `0`.
> +        Self {
> +            value: Opaque::uninit(),
> +            init: Atomic::new(0),
> +        }
> +    }
> +
> +    /// Get a reference to the contained object.
> +    ///
> +    /// Returns [`None`] if this [`OnceLock`] is empty.
> +    pub fn as_ref(&self) -> Option<&T> {

Looks like the get method in the OnceLock.

> +        if self.init.load(Acquire) == 2 {
> +            // SAFETY: As determined by the load above, the object is ready for shared access.
> +            Some(unsafe { &*self.value.get() })
> +        } else {
> +            None
> +        }
> +    }
> +
> +    /// Populate the [`OnceLock`].
> +    ///
> +    /// Returns `true` if the [`OnceLock`] was successfully populated.
> +    pub fn populate(&self, value: T) -> bool {

Looks like set in OnceLock.

Might also be worth implementing get_or_{try,}init, which get the value 
while initializing.

> +        // INVARIANT: We obtain exclusive access to the contained allocation and write 1 to
> +        // `init`.
> +        if let Ok(0) = self.init.cmpxchg(0, 1, Acquire) {
> +            // SAFETY: We obtained exclusive access to the contained object.
> +            unsafe { core::ptr::write(self.value.get(), value) };
> +            // INVARIANT: We release our exclusive access and transition the object to shared
> +            // access.
> +            self.init.store(2, Release);
> +            true
> +        } else {
> +            false
> +        }
> +    }
> +}
> +
> +impl<T: Copy> OnceLock<T> {
> +    /// Get a copy of the contained object.
> +    ///
> +    /// Returns [`None`] if the [`OnceLock`] is empty.
> +    pub fn copy(&self) -> Option<T> {

No equivalent in OnceLock. Similar to something like this:

x.get().copied().unwrap(); // x is a OnceLock

Example:
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=f21068e55f73722544fb5ad341bce1c5

Maybe not specifically needed?

> +        if self.init.load(Acquire) == 2 {
> +            // SAFETY: As determined by the load above, the object is ready for shared access.
> +            Some(unsafe { *self.value.get() })
> +        } else {
> +            None
> +        }
> +    }
> +}
> 

-- 
You're more amazing than you think!
Re: [PATCH v14 1/7] rust: sync: add `OnceLock`
Posted by Andreas Hindborg 3 months ago
"Wren Turkal" <wt@penguintechs.org> writes:

> On 7/2/25 6:18 AM, Andreas Hindborg wrote:

[...]

>> +pub struct OnceLock<T> {
>> +    init: Atomic<u32>,
>> +    value: Opaque<T>,
>> +}
>
> This type looks very much like the Once type in rust's stdlib. I am
> wondering if the api could be changed to match that api. I know that
> this type is trying to provide a version subset of std::sync::OnceLock
> that doesn't allow resetting the type like these apis:
>
> * https://doc.rust-lang.org/std/sync/struct.OnceLock.html#method.get_mut
> * https://doc.rust-lang.org/std/sync/struct.OnceLock.html#method.take
>
> However, these methods can only be used on mut. See here for failing
> example:
> https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=a78e51203c5b9555e3c151e162f0acab
>
> I think it might make more sense to match the api of the stdlib API and
> maybe only implement the methods you need.

I agree, it would be nice to match the names to std. But I do not like
that they have `OnceLock::set`, `OnceLock::try_init` and
`OnceLock::get_or{_try}_init`. Why is it not `OnceLock::init` or
`OnceLock::try_set`?

>
>> +
>> +impl<T> Default for OnceLock<T> {
>> +    fn default() -> Self {
>> +        Self::new()
>> +    }
>> +}
>
> Any reason not to use #[derive(Default)]?

We don't have `Default` for neither `Atomic` or `Opaque`.

[...]

> Might also be worth implementing get_or_{try,}init, which get the value
> while initializing.

I did not have a user for those, so not adding for now. It would be dead
code. They should be fairly straight forward to add though.

>
>> +        // INVARIANT: We obtain exclusive access to the contained allocation and write 1 to
>> +        // `init`.
>> +        if let Ok(0) = self.init.cmpxchg(0, 1, Acquire) {
>> +            // SAFETY: We obtained exclusive access to the contained object.
>> +            unsafe { core::ptr::write(self.value.get(), value) };
>> +            // INVARIANT: We release our exclusive access and transition the object to shared
>> +            // access.
>> +            self.init.store(2, Release);
>> +            true
>> +        } else {
>> +            false
>> +        }
>> +    }
>> +}
>> +
>> +impl<T: Copy> OnceLock<T> {
>> +    /// Get a copy of the contained object.
>> +    ///
>> +    /// Returns [`None`] if the [`OnceLock`] is empty.
>> +    pub fn copy(&self) -> Option<T> {
>
> No equivalent in OnceLock. Similar to something like this:
>
> x.get().copied().unwrap(); // x is a OnceLock
>
> Example:
> https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=f21068e55f73722544fb5ad341bce1c5
>
> Maybe not specifically needed?

I don't actually have a user for this, so I think I will drop it.


Best regards,
Andreas Hindborg
Re: [PATCH v14 1/7] rust: sync: add `OnceLock`
Posted by Benno Lossin 3 months, 1 week ago
On Wed Jul 2, 2025 at 3:18 PM CEST, Andreas Hindborg wrote:
> Introduce the `OnceLock` type, a container that can only be written once.
> The container uses an internal atomic to synchronize writes to the internal
> value.
>
> Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
> ---
>  rust/kernel/sync.rs           |   1 +
>  rust/kernel/sync/once_lock.rs | 104 ++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 105 insertions(+)
>
> diff --git a/rust/kernel/sync.rs b/rust/kernel/sync.rs
> index c7c0e552bafe..f2ee07315091 100644
> --- a/rust/kernel/sync.rs
> +++ b/rust/kernel/sync.rs
> @@ -15,6 +15,7 @@
>  mod condvar;
>  pub mod lock;
>  mod locked_by;
> +pub mod once_lock;

As Alice already said, we should reexport the type. And then make the
module private, no need to have `kernel::sync::OnceLock` and
`kernel::sync::once_lock::OnceLock`...

Also, I agree with the name change to `SetOnce` or something similar.

>  pub mod poll;
>  pub mod rcu;
>  
> diff --git a/rust/kernel/sync/once_lock.rs b/rust/kernel/sync/once_lock.rs
> new file mode 100644
> index 000000000000..cd311bea3919
> --- /dev/null
> +++ b/rust/kernel/sync/once_lock.rs
> @@ -0,0 +1,104 @@
> +//! A container that can be initialized at most once.
> +
> +use super::atomic::ordering::Acquire;
> +use super::atomic::ordering::Release;
> +use super::atomic::Atomic;
> +use kernel::types::Opaque;
> +
> +/// A container that can be populated at most once. Thread safe.
> +///
> +/// Once the a [`OnceLock`] is populated, it remains populated by the same object for the
> +/// lifetime `Self`.
> +///
> +/// # Invariants
> +///
> +/// `init` tracks the state of the container:
> +///
> +/// - If the container is empty, `init` is `0`.
> +/// - If the container is mutably accessed, `init` is `1`.

I think we should swap the order and change the ifs to iffs:

    - `init == 0` iff the container is empty.
    - `init == 1` iff the container is being accessed mutably.

> +/// - If the container is populated and ready for shared access, `init` is `2`.

You also need that `init` is only increased and never decreases.
Otherwise you could read a `2` and then access the value, but `init`
changed under your nose to `0`.

Then the INVARIANT comments below also need to be updated.

> +///
> +/// # Example
> +///
> +/// ```
> +/// # use kernel::sync::once_lock::OnceLock;
> +/// let value = OnceLock::new();
> +/// assert_eq!(None, value.as_ref());
> +///
> +/// let status = value.populate(42u8);
> +/// assert_eq!(true, status);
> +/// assert_eq!(Some(&42u8), value.as_ref());
> +/// assert_eq!(Some(42u8), value.copy());
> +///
> +/// let status = value.populate(101u8);
> +/// assert_eq!(false, status);
> +/// assert_eq!(Some(&42u8), value.as_ref());
> +/// assert_eq!(Some(42u8), value.copy());
> +/// ```
> +pub struct OnceLock<T> {
> +    init: Atomic<u32>,
> +    value: Opaque<T>,
> +}
> +
> +impl<T> Default for OnceLock<T> {
> +    fn default() -> Self {
> +        Self::new()
> +    }
> +}
> +
> +impl<T> OnceLock<T> {
> +    /// Create a new [`OnceLock`].
> +    ///
> +    /// The returned instance will be empty.
> +    pub const fn new() -> Self {
> +        // INVARIANT: The container is empty and we set `init` to `0`.
> +        Self {
> +            value: Opaque::uninit(),
> +            init: Atomic::new(0),
> +        }
> +    }
> +
> +    /// Get a reference to the contained object.
> +    ///
> +    /// Returns [`None`] if this [`OnceLock`] is empty.
> +    pub fn as_ref(&self) -> Option<&T> {
> +        if self.init.load(Acquire) == 2 {
> +            // SAFETY: As determined by the load above, the object is ready for shared access.

    // SAFETY: By the safety requirements of `Self`, `self.init == 2` means that `self.value` contains
    // a valid value.

> +            Some(unsafe { &*self.value.get() })
> +        } else {
> +            None
> +        }
> +    }
> +
> +    /// Populate the [`OnceLock`].
> +    ///
> +    /// Returns `true` if the [`OnceLock`] was successfully populated.
> +    pub fn populate(&self, value: T) -> bool {
> +        // INVARIANT: We obtain exclusive access to the contained allocation and write 1 to
> +        // `init`.
> +        if let Ok(0) = self.init.cmpxchg(0, 1, Acquire) {
> +            // SAFETY: We obtained exclusive access to the contained object.
> +            unsafe { core::ptr::write(self.value.get(), value) };
> +            // INVARIANT: We release our exclusive access and transition the object to shared
> +            // access.
> +            self.init.store(2, Release);
> +            true
> +        } else {
> +            false
> +        }
> +    }
> +}
> +
> +impl<T: Copy> OnceLock<T> {
> +    /// Get a copy of the contained object.
> +    ///
> +    /// Returns [`None`] if the [`OnceLock`] is empty.
> +    pub fn copy(&self) -> Option<T> {
> +        if self.init.load(Acquire) == 2 {
> +            // SAFETY: As determined by the load above, the object is ready for shared access.
> +            Some(unsafe { *self.value.get() })
> +        } else {
> +            None
> +        }

The impl can just be:

    self.as_ref().copied()

Would it make sense for this function to take `self` instead & we make
the `OnceLock` also `Copy` if `T: Copy`? Maybe not...

> +    }
> +}

You can move this method into the block above and just add `where T:
Copy` on the method.

---
Cheers,
Benno
Re: [PATCH v14 1/7] rust: sync: add `OnceLock`
Posted by Andreas Hindborg 3 months ago
"Benno Lossin" <lossin@kernel.org> writes:

> On Wed Jul 2, 2025 at 3:18 PM CEST, Andreas Hindborg wrote:
>> Introduce the `OnceLock` type, a container that can only be written once.
>> The container uses an internal atomic to synchronize writes to the internal
>> value.
>>
>> Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
>> ---
>>  rust/kernel/sync.rs           |   1 +
>>  rust/kernel/sync/once_lock.rs | 104 ++++++++++++++++++++++++++++++++++++++++++
>>  2 files changed, 105 insertions(+)
>>
>> diff --git a/rust/kernel/sync.rs b/rust/kernel/sync.rs
>> index c7c0e552bafe..f2ee07315091 100644
>> --- a/rust/kernel/sync.rs
>> +++ b/rust/kernel/sync.rs
>> @@ -15,6 +15,7 @@
>>  mod condvar;
>>  pub mod lock;
>>  mod locked_by;
>> +pub mod once_lock;
>
> As Alice already said, we should reexport the type. And then make the
> module private, no need to have `kernel::sync::OnceLock` and
> `kernel::sync::once_lock::OnceLock`...

Will do.

>
> Also, I agree with the name change to `SetOnce` or something similar.

I'm OK with that, but please see my comments on Alice suggestion.

>
>>  pub mod poll;
>>  pub mod rcu;
>>
>> diff --git a/rust/kernel/sync/once_lock.rs b/rust/kernel/sync/once_lock.rs
>> new file mode 100644
>> index 000000000000..cd311bea3919
>> --- /dev/null
>> +++ b/rust/kernel/sync/once_lock.rs
>> @@ -0,0 +1,104 @@
>> +//! A container that can be initialized at most once.
>> +
>> +use super::atomic::ordering::Acquire;
>> +use super::atomic::ordering::Release;
>> +use super::atomic::Atomic;
>> +use kernel::types::Opaque;
>> +
>> +/// A container that can be populated at most once. Thread safe.
>> +///
>> +/// Once the a [`OnceLock`] is populated, it remains populated by the same object for the
>> +/// lifetime `Self`.
>> +///
>> +/// # Invariants
>> +///
>> +/// `init` tracks the state of the container:
>> +///
>> +/// - If the container is empty, `init` is `0`.
>> +/// - If the container is mutably accessed, `init` is `1`.
>
> I think we should swap the order and change the ifs to iffs:
>
>     - `init == 0` iff the container is empty.
>     - `init == 1` iff the container is being accessed mutably.

Right, that is better, but I will expand "iff".

>
>> +/// - If the container is populated and ready for shared access, `init` is `2`.
>
> You also need that `init` is only increased and never decreases.
> Otherwise you could read a `2` and then access the value, but `init`
> changed under your nose to `0`.
>
> Then the INVARIANT comments below also need to be updated.

OK.

>
>> +///
>> +/// # Example
>> +///
>> +/// ```
>> +/// # use kernel::sync::once_lock::OnceLock;
>> +/// let value = OnceLock::new();
>> +/// assert_eq!(None, value.as_ref());
>> +///
>> +/// let status = value.populate(42u8);
>> +/// assert_eq!(true, status);
>> +/// assert_eq!(Some(&42u8), value.as_ref());
>> +/// assert_eq!(Some(42u8), value.copy());
>> +///
>> +/// let status = value.populate(101u8);
>> +/// assert_eq!(false, status);
>> +/// assert_eq!(Some(&42u8), value.as_ref());
>> +/// assert_eq!(Some(42u8), value.copy());
>> +/// ```
>> +pub struct OnceLock<T> {
>> +    init: Atomic<u32>,
>> +    value: Opaque<T>,
>> +}
>> +
>> +impl<T> Default for OnceLock<T> {
>> +    fn default() -> Self {
>> +        Self::new()
>> +    }
>> +}
>> +
>> +impl<T> OnceLock<T> {
>> +    /// Create a new [`OnceLock`].
>> +    ///
>> +    /// The returned instance will be empty.
>> +    pub const fn new() -> Self {
>> +        // INVARIANT: The container is empty and we set `init` to `0`.
>> +        Self {
>> +            value: Opaque::uninit(),
>> +            init: Atomic::new(0),
>> +        }
>> +    }
>> +
>> +    /// Get a reference to the contained object.
>> +    ///
>> +    /// Returns [`None`] if this [`OnceLock`] is empty.
>> +    pub fn as_ref(&self) -> Option<&T> {
>> +        if self.init.load(Acquire) == 2 {
>> +            // SAFETY: As determined by the load above, the object is ready for shared access.
>
>     // SAFETY: By the safety requirements of `Self`, `self.init == 2` means that `self.value` contains
>     // a valid value.

By the *type invariants* I guess?

>
>> +            Some(unsafe { &*self.value.get() })
>> +        } else {
>> +            None
>> +        }
>> +    }
>> +
>> +    /// Populate the [`OnceLock`].
>> +    ///
>> +    /// Returns `true` if the [`OnceLock`] was successfully populated.
>> +    pub fn populate(&self, value: T) -> bool {
>> +        // INVARIANT: We obtain exclusive access to the contained allocation and write 1 to
>> +        // `init`.
>> +        if let Ok(0) = self.init.cmpxchg(0, 1, Acquire) {
>> +            // SAFETY: We obtained exclusive access to the contained object.
>> +            unsafe { core::ptr::write(self.value.get(), value) };
>> +            // INVARIANT: We release our exclusive access and transition the object to shared
>> +            // access.
>> +            self.init.store(2, Release);
>> +            true
>> +        } else {
>> +            false
>> +        }
>> +    }
>> +}
>> +
>> +impl<T: Copy> OnceLock<T> {
>> +    /// Get a copy of the contained object.
>> +    ///
>> +    /// Returns [`None`] if the [`OnceLock`] is empty.
>> +    pub fn copy(&self) -> Option<T> {
>> +        if self.init.load(Acquire) == 2 {
>> +            // SAFETY: As determined by the load above, the object is ready for shared access.
>> +            Some(unsafe { *self.value.get() })
>> +        } else {
>> +            None
>> +        }
>
> The impl can just be:
>
>     self.as_ref().copied()

Nice. I was thinking of dropping this method and just have callers do

 my_once_lock.as_ref().map(|v| v.copied())

What do you think?


Best regards,
Andreas Hindborg
Re: [PATCH v14 1/7] rust: sync: add `OnceLock`
Posted by Benno Lossin 3 months ago
On Thu Jul 3, 2025 at 11:03 AM CEST, Andreas Hindborg wrote:
> "Benno Lossin" <lossin@kernel.org> writes:
>> On Wed Jul 2, 2025 at 3:18 PM CEST, Andreas Hindborg wrote:
>>> +///
>>> +/// # Example
>>> +///
>>> +/// ```
>>> +/// # use kernel::sync::once_lock::OnceLock;
>>> +/// let value = OnceLock::new();
>>> +/// assert_eq!(None, value.as_ref());
>>> +///
>>> +/// let status = value.populate(42u8);
>>> +/// assert_eq!(true, status);
>>> +/// assert_eq!(Some(&42u8), value.as_ref());
>>> +/// assert_eq!(Some(42u8), value.copy());
>>> +///
>>> +/// let status = value.populate(101u8);
>>> +/// assert_eq!(false, status);
>>> +/// assert_eq!(Some(&42u8), value.as_ref());
>>> +/// assert_eq!(Some(42u8), value.copy());
>>> +/// ```
>>> +pub struct OnceLock<T> {
>>> +    init: Atomic<u32>,
>>> +    value: Opaque<T>,
>>> +}
>>> +
>>> +impl<T> Default for OnceLock<T> {
>>> +    fn default() -> Self {
>>> +        Self::new()
>>> +    }
>>> +}
>>> +
>>> +impl<T> OnceLock<T> {
>>> +    /// Create a new [`OnceLock`].
>>> +    ///
>>> +    /// The returned instance will be empty.
>>> +    pub const fn new() -> Self {
>>> +        // INVARIANT: The container is empty and we set `init` to `0`.
>>> +        Self {
>>> +            value: Opaque::uninit(),
>>> +            init: Atomic::new(0),
>>> +        }
>>> +    }
>>> +
>>> +    /// Get a reference to the contained object.
>>> +    ///
>>> +    /// Returns [`None`] if this [`OnceLock`] is empty.
>>> +    pub fn as_ref(&self) -> Option<&T> {
>>> +        if self.init.load(Acquire) == 2 {
>>> +            // SAFETY: As determined by the load above, the object is ready for shared access.
>>
>>     // SAFETY: By the safety requirements of `Self`, `self.init == 2` means that `self.value` contains
>>     // a valid value.
>
> By the *type invariants* I guess?

Oh yeah.

>>> +            Some(unsafe { &*self.value.get() })
>>> +        } else {
>>> +            None
>>> +        }
>>> +    }
>>> +
>>> +    /// Populate the [`OnceLock`].
>>> +    ///
>>> +    /// Returns `true` if the [`OnceLock`] was successfully populated.
>>> +    pub fn populate(&self, value: T) -> bool {
>>> +        // INVARIANT: We obtain exclusive access to the contained allocation and write 1 to
>>> +        // `init`.
>>> +        if let Ok(0) = self.init.cmpxchg(0, 1, Acquire) {
>>> +            // SAFETY: We obtained exclusive access to the contained object.
>>> +            unsafe { core::ptr::write(self.value.get(), value) };
>>> +            // INVARIANT: We release our exclusive access and transition the object to shared
>>> +            // access.
>>> +            self.init.store(2, Release);
>>> +            true
>>> +        } else {
>>> +            false
>>> +        }
>>> +    }
>>> +}
>>> +
>>> +impl<T: Copy> OnceLock<T> {
>>> +    /// Get a copy of the contained object.
>>> +    ///
>>> +    /// Returns [`None`] if the [`OnceLock`] is empty.
>>> +    pub fn copy(&self) -> Option<T> {
>>> +        if self.init.load(Acquire) == 2 {
>>> +            // SAFETY: As determined by the load above, the object is ready for shared access.
>>> +            Some(unsafe { *self.value.get() })
>>> +        } else {
>>> +            None
>>> +        }
>>
>> The impl can just be:
>>
>>     self.as_ref().copied()
>
> Nice. I was thinking of dropping this method and just have callers do
>
>  my_once_lock.as_ref().map(|v| v.copied())
>
> What do you think?

There is `Option::copied`, so no need for the `.map` call. I don't
really have a preference, if users always want to access it by-value,
then we should have `copy`.

---
Cheers,
Benno
Re: [PATCH v14 1/7] rust: sync: add `OnceLock`
Posted by Andreas Hindborg 3 months ago
"Benno Lossin" <lossin@kernel.org> writes:

> On Thu Jul 3, 2025 at 11:03 AM CEST, Andreas Hindborg wrote:
>> "Benno Lossin" <lossin@kernel.org> writes:
>>> On Wed Jul 2, 2025 at 3:18 PM CEST, Andreas Hindborg wrote:

[...]

>>>> +            Some(unsafe { &*self.value.get() })
>>>> +        } else {
>>>> +            None
>>>> +        }
>>>> +    }
>>>> +
>>>> +    /// Populate the [`OnceLock`].
>>>> +    ///
>>>> +    /// Returns `true` if the [`OnceLock`] was successfully populated.
>>>> +    pub fn populate(&self, value: T) -> bool {
>>>> +        // INVARIANT: We obtain exclusive access to the contained allocation and write 1 to
>>>> +        // `init`.
>>>> +        if let Ok(0) = self.init.cmpxchg(0, 1, Acquire) {
>>>> +            // SAFETY: We obtained exclusive access to the contained object.
>>>> +            unsafe { core::ptr::write(self.value.get(), value) };
>>>> +            // INVARIANT: We release our exclusive access and transition the object to shared
>>>> +            // access.
>>>> +            self.init.store(2, Release);
>>>> +            true
>>>> +        } else {
>>>> +            false
>>>> +        }
>>>> +    }
>>>> +}
>>>> +
>>>> +impl<T: Copy> OnceLock<T> {
>>>> +    /// Get a copy of the contained object.
>>>> +    ///
>>>> +    /// Returns [`None`] if the [`OnceLock`] is empty.
>>>> +    pub fn copy(&self) -> Option<T> {
>>>> +        if self.init.load(Acquire) == 2 {
>>>> +            // SAFETY: As determined by the load above, the object is ready for shared access.
>>>> +            Some(unsafe { *self.value.get() })
>>>> +        } else {
>>>> +            None
>>>> +        }
>>>
>>> The impl can just be:
>>>
>>>     self.as_ref().copied()
>>
>> Nice. I was thinking of dropping this method and just have callers do
>>
>>  my_once_lock.as_ref().map(|v| v.copied())
>>
>> What do you think?
>
> There is `Option::copied`, so no need for the `.map` call.

Cool.

> I don't
> really have a preference, if users always want to access it by-value,
> then we should have `copy`.

But should it be `copy` or `copied` like `Option`?


Best regards,
Andreas Hindborg
Re: [PATCH v14 1/7] rust: sync: add `OnceLock`
Posted by Benno Lossin 3 months ago
On Thu Jul 3, 2025 at 6:25 PM CEST, Andreas Hindborg wrote:
> "Benno Lossin" <lossin@kernel.org> writes:
>> On Thu Jul 3, 2025 at 11:03 AM CEST, Andreas Hindborg wrote:
>>> "Benno Lossin" <lossin@kernel.org> writes:
>>>> On Wed Jul 2, 2025 at 3:18 PM CEST, Andreas Hindborg wrote:
>>>>> +impl<T: Copy> OnceLock<T> {
>>>>> +    /// Get a copy of the contained object.
>>>>> +    ///
>>>>> +    /// Returns [`None`] if the [`OnceLock`] is empty.
>>>>> +    pub fn copy(&self) -> Option<T> {
>>>>> +        if self.init.load(Acquire) == 2 {
>>>>> +            // SAFETY: As determined by the load above, the object is ready for shared access.
>>>>> +            Some(unsafe { *self.value.get() })
>>>>> +        } else {
>>>>> +            None
>>>>> +        }
>>>>
>>>> The impl can just be:
>>>>
>>>>     self.as_ref().copied()
>>>
>>> Nice. I was thinking of dropping this method and just have callers do
>>>
>>>  my_once_lock.as_ref().map(|v| v.copied())
>>>
>>> What do you think?
>>
>> There is `Option::copied`, so no need for the `.map` call.
>
> Cool.
>
>> I don't
>> really have a preference, if users always want to access it by-value,
>> then we should have `copy`.
>
> But should it be `copy` or `copied` like `Option`?

I'd say `copy`. With `copied` I'm thinking of something that turns
`OnceLock<&T>` into `OnceLock<T>`, which is weird...

---
Cheers,
Benno
Re: [PATCH v14 1/7] rust: sync: add `OnceLock`
Posted by Alice Ryhl 3 months, 1 week ago
On Wed, Jul 2, 2025 at 5:07 PM Benno Lossin <lossin@kernel.org> wrote:
>
> On Wed Jul 2, 2025 at 3:18 PM CEST, Andreas Hindborg wrote:
> > +impl<T: Copy> OnceLock<T> {
> > +    /// Get a copy of the contained object.
> > +    ///
> > +    /// Returns [`None`] if the [`OnceLock`] is empty.
> > +    pub fn copy(&self) -> Option<T> {
> > +        if self.init.load(Acquire) == 2 {
> > +            // SAFETY: As determined by the load above, the object is ready for shared access.
> > +            Some(unsafe { *self.value.get() })
> > +        } else {
> > +            None
> > +        }
>
> The impl can just be:
>
>     self.as_ref().copied()
>
> Would it make sense for this function to take `self` instead & we make
> the `OnceLock` also `Copy` if `T: Copy`? Maybe not...

Atomics are not Copy.

Alice
Re: [PATCH v14 1/7] rust: sync: add `OnceLock`
Posted by Benno Lossin 3 months, 1 week ago
On Wed Jul 2, 2025 at 5:27 PM CEST, Alice Ryhl wrote:
> On Wed, Jul 2, 2025 at 5:07 PM Benno Lossin <lossin@kernel.org> wrote:
>> On Wed Jul 2, 2025 at 3:18 PM CEST, Andreas Hindborg wrote:
>> > +impl<T: Copy> OnceLock<T> {
>> > +    /// Get a copy of the contained object.
>> > +    ///
>> > +    /// Returns [`None`] if the [`OnceLock`] is empty.
>> > +    pub fn copy(&self) -> Option<T> {
>> > +        if self.init.load(Acquire) == 2 {
>> > +            // SAFETY: As determined by the load above, the object is ready for shared access.
>> > +            Some(unsafe { *self.value.get() })
>> > +        } else {
>> > +            None
>> > +        }
>>
>> The impl can just be:
>>
>>     self.as_ref().copied()
>>
>> Would it make sense for this function to take `self` instead & we make
>> the `OnceLock` also `Copy` if `T: Copy`? Maybe not...
>
> Atomics are not Copy.

Ah right... Yeah it probably also isn't useful.

---
Cheers,
Benno
Re: [PATCH v14 1/7] rust: sync: add `OnceLock`
Posted by Alice Ryhl 3 months, 1 week ago
On Wed, Jul 2, 2025 at 3:19 PM Andreas Hindborg <a.hindborg@kernel.org> wrote:
>
> Introduce the `OnceLock` type, a container that can only be written once.
> The container uses an internal atomic to synchronize writes to the internal
> value.
>
> Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>

This type provides no way to wait for initialization to finish if it's
ongoing. Do you not need that?

> ---
>  rust/kernel/sync.rs           |   1 +
>  rust/kernel/sync/once_lock.rs | 104 ++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 105 insertions(+)
>
> diff --git a/rust/kernel/sync.rs b/rust/kernel/sync.rs
> index c7c0e552bafe..f2ee07315091 100644
> --- a/rust/kernel/sync.rs
> +++ b/rust/kernel/sync.rs
> @@ -15,6 +15,7 @@
>  mod condvar;
>  pub mod lock;
>  mod locked_by;
> +pub mod once_lock;

I would add a re-export so that users can import this as kernel::sync::OnceLock.

>  pub mod poll;
>  pub mod rcu;
>
> diff --git a/rust/kernel/sync/once_lock.rs b/rust/kernel/sync/once_lock.rs
> new file mode 100644
> index 000000000000..cd311bea3919
> --- /dev/null
> +++ b/rust/kernel/sync/once_lock.rs
> @@ -0,0 +1,104 @@
> +//! A container that can be initialized at most once.
> +
> +use super::atomic::ordering::Acquire;
> +use super::atomic::ordering::Release;
> +use super::atomic::Atomic;
> +use kernel::types::Opaque;
> +
> +/// A container that can be populated at most once. Thread safe.
> +///
> +/// Once the a [`OnceLock`] is populated, it remains populated by the same object for the
> +/// lifetime `Self`.
> +///
> +/// # Invariants
> +///
> +/// `init` tracks the state of the container:
> +///
> +/// - If the container is empty, `init` is `0`.
> +/// - If the container is mutably accessed, `init` is `1`.

I would phrase this as "being initialized" instead of "mutably
accessed". I initially thought this was talking about someone calling
a &mut self method.

> +/// - If the container is populated and ready for shared access, `init` is `2`.
> +///
> +/// # Example
> +///
> +/// ```
> +/// # use kernel::sync::once_lock::OnceLock;
> +/// let value = OnceLock::new();
> +/// assert_eq!(None, value.as_ref());
> +///
> +/// let status = value.populate(42u8);
> +/// assert_eq!(true, status);
> +/// assert_eq!(Some(&42u8), value.as_ref());
> +/// assert_eq!(Some(42u8), value.copy());
> +///
> +/// let status = value.populate(101u8);
> +/// assert_eq!(false, status);
> +/// assert_eq!(Some(&42u8), value.as_ref());
> +/// assert_eq!(Some(42u8), value.copy());
> +/// ```
> +pub struct OnceLock<T> {
> +    init: Atomic<u32>,
> +    value: Opaque<T>,

Opaque does not destroy the inner value. You are missing a destructor.

> +}
> +
> +impl<T> Default for OnceLock<T> {
> +    fn default() -> Self {
> +        Self::new()
> +    }
> +}
> +
> +impl<T> OnceLock<T> {
> +    /// Create a new [`OnceLock`].
> +    ///
> +    /// The returned instance will be empty.
> +    pub const fn new() -> Self {
> +        // INVARIANT: The container is empty and we set `init` to `0`.
> +        Self {
> +            value: Opaque::uninit(),
> +            init: Atomic::new(0),
> +        }
> +    }
> +
> +    /// Get a reference to the contained object.
> +    ///
> +    /// Returns [`None`] if this [`OnceLock`] is empty.
> +    pub fn as_ref(&self) -> Option<&T> {
> +        if self.init.load(Acquire) == 2 {
> +            // SAFETY: As determined by the load above, the object is ready for shared access.
> +            Some(unsafe { &*self.value.get() })
> +        } else {
> +            None
> +        }
> +    }
> +
> +    /// Populate the [`OnceLock`].
> +    ///
> +    /// Returns `true` if the [`OnceLock`] was successfully populated.
> +    pub fn populate(&self, value: T) -> bool {
> +        // INVARIANT: We obtain exclusive access to the contained allocation and write 1 to
> +        // `init`.
> +        if let Ok(0) = self.init.cmpxchg(0, 1, Acquire) {

This acquire can be Relaxed. All other accesses to self.value
synchronize with the release store below, so you do not need acquire
here to obtain exclusive access.

> +            // SAFETY: We obtained exclusive access to the contained object.
> +            unsafe { core::ptr::write(self.value.get(), value) };
> +            // INVARIANT: We release our exclusive access and transition the object to shared
> +            // access.
> +            self.init.store(2, Release);
> +            true
> +        } else {
> +            false
> +        }
> +    }
> +}
> +
> +impl<T: Copy> OnceLock<T> {
> +    /// Get a copy of the contained object.
> +    ///
> +    /// Returns [`None`] if the [`OnceLock`] is empty.
> +    pub fn copy(&self) -> Option<T> {
> +        if self.init.load(Acquire) == 2 {
> +            // SAFETY: As determined by the load above, the object is ready for shared access.
> +            Some(unsafe { *self.value.get() })
> +        } else {
> +            None
> +        }
> +    }
> +}
>
> --
> 2.47.2
>
>
Re: [PATCH v14 1/7] rust: sync: add `OnceLock`
Posted by Andreas Hindborg 3 months, 1 week ago
"Alice Ryhl" <aliceryhl@google.com> writes:

> On Wed, Jul 2, 2025 at 3:19 PM Andreas Hindborg <a.hindborg@kernel.org> wrote:
>>
>> Introduce the `OnceLock` type, a container that can only be written once.
>> The container uses an internal atomic to synchronize writes to the internal
>> value.
>>
>> Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
>
> This type provides no way to wait for initialization to finish if it's
> ongoing. Do you not need that?

I don't, and in my use case it would cause a deadlock to wait. Anyway,
it might be useful to others. Would you add it now, or wait for a user?

>
>> ---
>>  rust/kernel/sync.rs           |   1 +
>>  rust/kernel/sync/once_lock.rs | 104 ++++++++++++++++++++++++++++++++++++++++++
>>  2 files changed, 105 insertions(+)
>>
>> diff --git a/rust/kernel/sync.rs b/rust/kernel/sync.rs
>> index c7c0e552bafe..f2ee07315091 100644
>> --- a/rust/kernel/sync.rs
>> +++ b/rust/kernel/sync.rs
>> @@ -15,6 +15,7 @@
>>  mod condvar;
>>  pub mod lock;
>>  mod locked_by;
>> +pub mod once_lock;
>
> I would add a re-export so that users can import this as kernel::sync::OnceLock.

OK.

>
>>  pub mod poll;
>>  pub mod rcu;
>>
>> diff --git a/rust/kernel/sync/once_lock.rs b/rust/kernel/sync/once_lock.rs
>> new file mode 100644
>> index 000000000000..cd311bea3919
>> --- /dev/null
>> +++ b/rust/kernel/sync/once_lock.rs
>> @@ -0,0 +1,104 @@
>> +//! A container that can be initialized at most once.
>> +
>> +use super::atomic::ordering::Acquire;
>> +use super::atomic::ordering::Release;
>> +use super::atomic::Atomic;
>> +use kernel::types::Opaque;
>> +
>> +/// A container that can be populated at most once. Thread safe.
>> +///
>> +/// Once the a [`OnceLock`] is populated, it remains populated by the same object for the
>> +/// lifetime `Self`.
>> +///
>> +/// # Invariants
>> +///
>> +/// `init` tracks the state of the container:
>> +///
>> +/// - If the container is empty, `init` is `0`.
>> +/// - If the container is mutably accessed, `init` is `1`.
>
> I would phrase this as "being initialized" instead of "mutably
> accessed". I initially thought this was talking about someone calling
> a &mut self method.

Makes sense, I will change that.

>
>> +/// - If the container is populated and ready for shared access, `init` is `2`.
>> +///
>> +/// # Example
>> +///
>> +/// ```
>> +/// # use kernel::sync::once_lock::OnceLock;
>> +/// let value = OnceLock::new();
>> +/// assert_eq!(None, value.as_ref());
>> +///
>> +/// let status = value.populate(42u8);
>> +/// assert_eq!(true, status);
>> +/// assert_eq!(Some(&42u8), value.as_ref());
>> +/// assert_eq!(Some(42u8), value.copy());
>> +///
>> +/// let status = value.populate(101u8);
>> +/// assert_eq!(false, status);
>> +/// assert_eq!(Some(&42u8), value.as_ref());
>> +/// assert_eq!(Some(42u8), value.copy());
>> +/// ```
>> +pub struct OnceLock<T> {
>> +    init: Atomic<u32>,
>> +    value: Opaque<T>,
>
> Opaque does not destroy the inner value. You are missing a destructor.

Oops.

>
>> +}
>> +
>> +impl<T> Default for OnceLock<T> {
>> +    fn default() -> Self {
>> +        Self::new()
>> +    }
>> +}
>> +
>> +impl<T> OnceLock<T> {
>> +    /// Create a new [`OnceLock`].
>> +    ///
>> +    /// The returned instance will be empty.
>> +    pub const fn new() -> Self {
>> +        // INVARIANT: The container is empty and we set `init` to `0`.
>> +        Self {
>> +            value: Opaque::uninit(),
>> +            init: Atomic::new(0),
>> +        }
>> +    }
>> +
>> +    /// Get a reference to the contained object.
>> +    ///
>> +    /// Returns [`None`] if this [`OnceLock`] is empty.
>> +    pub fn as_ref(&self) -> Option<&T> {
>> +        if self.init.load(Acquire) == 2 {
>> +            // SAFETY: As determined by the load above, the object is ready for shared access.
>> +            Some(unsafe { &*self.value.get() })
>> +        } else {
>> +            None
>> +        }
>> +    }
>> +
>> +    /// Populate the [`OnceLock`].
>> +    ///
>> +    /// Returns `true` if the [`OnceLock`] was successfully populated.
>> +    pub fn populate(&self, value: T) -> bool {
>> +        // INVARIANT: We obtain exclusive access to the contained allocation and write 1 to
>> +        // `init`.
>> +        if let Ok(0) = self.init.cmpxchg(0, 1, Acquire) {
>
> This acquire can be Relaxed. All other accesses to self.value
> synchronize with the release store below, so you do not need acquire
> here to obtain exclusive access.

Right, thanks.


Best regards,
Andreas Hindborg
Re: [PATCH v14 1/7] rust: sync: add `OnceLock`
Posted by Alice Ryhl 3 months, 1 week ago
On Wed, Jul 2, 2025 at 3:54 PM Andreas Hindborg <a.hindborg@kernel.org> wrote:
>
> "Alice Ryhl" <aliceryhl@google.com> writes:
>
> > On Wed, Jul 2, 2025 at 3:19 PM Andreas Hindborg <a.hindborg@kernel.org> wrote:
> >>
> >> Introduce the `OnceLock` type, a container that can only be written once.
> >> The container uses an internal atomic to synchronize writes to the internal
> >> value.
> >>
> >> Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
> >
> > This type provides no way to wait for initialization to finish if it's
> > ongoing. Do you not need that?
>
> I don't, and in my use case it would cause a deadlock to wait. Anyway,
> it might be useful to others. Would you add it now, or wait for a user?

Waiting would require additional fields so it should probably be a
different type. It's more that we probably want the OnceLock name for
that other type for consistency with stdlib, so perhaps this should be
renamed? The name could be SetOnce or similar.

Alice
Re: [PATCH v14 1/7] rust: sync: add `OnceLock`
Posted by Andreas Hindborg 3 months ago
"Alice Ryhl" <aliceryhl@google.com> writes:

> On Wed, Jul 2, 2025 at 3:54 PM Andreas Hindborg <a.hindborg@kernel.org> wrote:
>>
>> "Alice Ryhl" <aliceryhl@google.com> writes:
>>
>> > On Wed, Jul 2, 2025 at 3:19 PM Andreas Hindborg <a.hindborg@kernel.org> wrote:
>> >>
>> >> Introduce the `OnceLock` type, a container that can only be written once.
>> >> The container uses an internal atomic to synchronize writes to the internal
>> >> value.
>> >>
>> >> Signed-off-by: Andreas Hindborg <a.hindborg@kernel.org>
>> >
>> > This type provides no way to wait for initialization to finish if it's
>> > ongoing. Do you not need that?
>>
>> I don't, and in my use case it would cause a deadlock to wait. Anyway,
>> it might be useful to others. Would you add it now, or wait for a user?
>
> Waiting would require additional fields so it should probably be a
> different type.

That depends on the kind of waiting. If we do unfair waiting, with
spinning, we can have unlimited waiters with this type. We can also use
the remaining 29 bits of the atomic to encode ordering to get fair
queuing for spinning waiters.

Putting waiters to sleep would require more fields.

> It's more that we probably want the OnceLock name for
> that other type for consistency with stdlib, so perhaps this should be
> renamed? The name could be SetOnce or similar.

The feature set is very similar to `std::sync::OnceLock`, that is why I
picked that name. We can expand this to allow resetting without too much
effort, and we can do fair waiting with spinning for a reasonable amount
of waiters.

But I am also OK with changing the name to `SetOnce` if we envision a
`OnceLock` with thread sleep blocking at some point.


Best regards,
Andreas Hindborg