aboutsummaryrefslogtreecommitdiff
path: root/embassy-sync/src
diff options
context:
space:
mode:
authorSamuel Tardieu <[email protected]>2024-07-27 11:49:02 +0200
committerSamuel Tardieu <[email protected]>2024-07-29 11:04:59 +0200
commit05e0f128462ebdacd3dffc27f74502913d782589 (patch)
treeb13be999acbf294a687f2d55165eea1cc9543073 /embassy-sync/src
parent4e5a646f8b1905de014462f5f0441952ec7e209b (diff)
embassy-sync: add LazyLock
`LazyLock` is inspired by Rust 1.80.0's `std::sync::LazyLock` type.
Diffstat (limited to 'embassy-sync/src')
-rw-r--r--embassy-sync/src/lazy_lock.rs84
-rw-r--r--embassy-sync/src/lib.rs1
2 files changed, 85 insertions, 0 deletions
diff --git a/embassy-sync/src/lazy_lock.rs b/embassy-sync/src/lazy_lock.rs
new file mode 100644
index 000000000..2b5742491
--- /dev/null
+++ b/embassy-sync/src/lazy_lock.rs
@@ -0,0 +1,84 @@
1//! Synchronization primitive for initializing a value once, allowing others to get a reference to the value.
2
3use core::cell::Cell;
4use core::mem::MaybeUninit;
5use core::sync::atomic::{AtomicBool, Ordering};
6
7/// The `LazyLock` is a synchronization primitive that allows for
8/// initializing a value once, and allowing others to obtain a
9/// reference to the value. This is useful for lazy initialization of
10/// a static value.
11///
12/// # Example
13/// ```
14/// use futures_executor::block_on;
15/// use embassy_sync::lazy_lock::LazyLock;
16///
17/// // Define a static value that will be lazily initialized
18/// // at runtime at the first access.
19/// static VALUE: LazyLock<u32> = LazyLock::new(|| 20);
20///
21/// let reference = VALUE.get();
22/// assert_eq!(reference, &20);
23/// ```
24pub struct LazyLock<T, F = fn() -> T> {
25 init: AtomicBool,
26 init_fn: Cell<Option<F>>,
27 data: Cell<MaybeUninit<T>>,
28}
29
30unsafe impl<T, F> Sync for LazyLock<T, F> {}
31
32impl<T, F: FnOnce() -> T> LazyLock<T, F> {
33 /// Create a new uninitialized `StaticLock`.
34 pub const fn new(init_fn: F) -> Self {
35 Self {
36 init: AtomicBool::new(false),
37 init_fn: Cell::new(Some(init_fn)),
38 data: Cell::new(MaybeUninit::zeroed()),
39 }
40 }
41
42 /// Get a reference to the underlying value, initializing it if it
43 /// has not been done already.
44 #[inline]
45 pub fn get(&self) -> &T {
46 self.ensure_init_fast();
47 unsafe { (*self.data.as_ptr()).assume_init_ref() }
48 }
49
50 /// Consume the `LazyLock`, returning the underlying value. The
51 /// initialization function will be called if it has not been
52 /// already.
53 #[inline]
54 pub fn into_inner(self) -> T {
55 self.ensure_init_fast();
56 unsafe { self.data.into_inner().assume_init() }
57 }
58
59 /// Initialize the `LazyLock` if it has not been initialized yet.
60 /// This function is a fast track to [`Self::ensure_init`]
61 /// which does not require a critical section in most cases when
62 /// the value has been initialized already.
63 /// When this function returns, `self.data` is guaranteed to be
64 /// initialized and visible on the current core.
65 #[inline]
66 fn ensure_init_fast(&self) {
67 if !self.init.load(Ordering::Acquire) {
68 self.ensure_init();
69 }
70 }
71
72 /// Initialize the `LazyLock` if it has not been initialized yet.
73 /// When this function returns, `self.data` is guaranteed to be
74 /// initialized and visible on the current core.
75 fn ensure_init(&self) {
76 critical_section::with(|_| {
77 if !self.init.load(Ordering::Acquire) {
78 let init_fn = self.init_fn.take().unwrap();
79 self.data.set(MaybeUninit::new(init_fn()));
80 self.init.store(true, Ordering::Release);
81 }
82 });
83 }
84}
diff --git a/embassy-sync/src/lib.rs b/embassy-sync/src/lib.rs
index a5eee8d02..014bf1d06 100644
--- a/embassy-sync/src/lib.rs
+++ b/embassy-sync/src/lib.rs
@@ -12,6 +12,7 @@ mod ring_buffer;
12 12
13pub mod blocking_mutex; 13pub mod blocking_mutex;
14pub mod channel; 14pub mod channel;
15pub mod lazy_lock;
15pub mod mutex; 16pub mod mutex;
16pub mod once_lock; 17pub mod once_lock;
17pub mod pipe; 18pub mod pipe;