once/
write_once.rs

1use std::{cell::UnsafeCell, mem::MaybeUninit, sync::atomic::Ordering};
2
3use crate::std_or_loom::{GetMut, sync::atomic::AtomicU8};
4
5/// A thread-safe, wait-free, write-once cell that allows a value to be set exactly once.
6///
7/// `WriteOnce` provides a way to initialize a value exactly once in a thread-safe manner. Once a
8/// value is set, it cannot be changed. This is useful in scenarios where you need to ensure that a
9/// value is initialized exactly once, even in the presence of concurrent access.
10///
11/// `WriteOnce` is similar to [`std::sync::OnceLock`], but with a key difference: it takes a value
12/// directly rather than a closure that initializes the value. In other words, we assume an
13/// optimistic concurrency model, contrary to `OnceLock`'s pessimistic model.
14///
15/// This concurrency model allows `WriteOnce` to be fully wait-free. This also implies that, whereas
16/// `OnceLock` guarantees that the closure is called at most once, `WriteOnce` guarantees that the
17/// value is *set* at most once.
18///
19/// # Thread Safety
20///
21/// `WriteOnce` is designed to be thread-safe and wait-free, allowing concurrent attempts to set the
22/// value from multiple threads. Only the first successful call to `set` or `try_set` will
23/// initialize the value; subsequent attempts will either panic (with `set`) or return an error
24/// (with `try_set`).
25///
26/// # Memory Safety
27///
28/// `WriteOnce` uses atomic operations to ensure memory safety and proper synchronization between
29/// threads. It guarantees that:
30///
31/// - A value can be set exactly once
32/// - Once set, the value can be safely read from any thread
33/// - The value is properly dropped when the `WriteOnce` is dropped
34///
35/// # Examples
36///
37/// ```
38/// use once::write_once::WriteOnce;
39///
40/// // Create a new WriteOnce with no value
41/// let cell = WriteOnce::<String>::none();
42///
43/// // Set the value
44/// cell.set("Hello, world!".to_string());
45///
46/// // Get the value
47/// assert_eq!(cell.get(), Some(&"Hello, world!".to_string()));
48///
49/// // Attempting to set the value again will panic
50/// // cell.set("Another value".to_string()); // This would panic
51///
52/// // Using try_set instead returns an error
53/// let result = cell.try_set("Another value".to_string());
54/// assert!(result.is_err());
55/// if let Err(value) = result {
56///     assert_eq!(value, "Another value".to_string());
57/// }
58/// ```
59pub struct WriteOnce<T> {
60    value: UnsafeCell<MaybeUninit<T>>,
61    state: AtomicU8,
62}
63
64impl<T> WriteOnce<T> {
65    /// Creates a new `WriteOnce` with no value.
66    ///
67    /// This initializes the cell in an empty state. The value can be set later using
68    /// [`set`](Self::set) or [`try_set`](Self::try_set).
69    ///
70    /// # Examples
71    ///
72    /// ```
73    /// use once::write_once::WriteOnce;
74    ///
75    /// let cell = WriteOnce::<i32>::none();
76    /// assert_eq!(cell.get(), None);
77    /// assert!(!cell.is_set());
78    /// ```
79    pub fn none() -> Self {
80        Self {
81            value: UnsafeCell::new(MaybeUninit::uninit()),
82            state: AtomicU8::new(WriteOnceState::Uninit as u8),
83        }
84    }
85
86    /// Creates a new `WriteOnce` with a value.
87    ///
88    /// # Examples
89    ///
90    /// ```
91    /// use once::write_once::WriteOnce;
92    ///
93    /// let cell = WriteOnce::new(2);
94    /// assert_eq!(cell.get(), Some(&2));
95    /// assert!(cell.is_set());
96    /// ```
97    pub fn new(value: T) -> Self {
98        Self {
99            value: UnsafeCell::new(MaybeUninit::new(value)),
100            state: AtomicU8::new(WriteOnceState::Init as u8),
101        }
102    }
103
104    /// Sets the value of the `WriteOnce`.
105    ///
106    /// This method sets the value of the cell if it hasn't been set yet.
107    /// If the cell already has a value, this method will panic.
108    ///
109    /// # Panics
110    ///
111    /// Panics if the cell already has a value.
112    ///
113    /// # Examples
114    ///
115    /// ```
116    /// use once::write_once::WriteOnce;
117    ///
118    /// let cell = WriteOnce::<i32>::none();
119    /// cell.set(42);
120    /// assert_eq!(cell.get(), Some(&42));
121    ///
122    /// // Uncommenting the following line would cause a panic:
123    /// // cell.set(100);
124    /// ```
125    pub fn set(&self, value: T) {
126        assert!(self.try_set(value).is_ok(), "WriteOnce already set");
127    }
128
129    /// Attempts to set the value of the `WriteOnce`.
130    ///
131    /// This method tries to set the value of the cell if it hasn't been set yet.
132    /// If the cell already has a value, this method will return an error containing
133    /// the value that was attempted to be set.
134    ///
135    /// # Returns
136    ///
137    /// - `Ok(())` if the value was successfully set
138    /// - `Err(value)` if the cell already had a value, returning the value that was
139    ///   attempted to be set
140    ///
141    /// # Examples
142    ///
143    /// ```
144    /// use once::write_once::WriteOnce;
145    ///
146    /// let cell = WriteOnce::<String>::none();
147    ///
148    /// // First attempt succeeds
149    /// let result = cell.try_set("Hello".to_string());
150    /// assert!(result.is_ok());
151    /// assert_eq!(cell.get(), Some(&"Hello".to_string()));
152    ///
153    /// // Second attempt fails
154    /// let result = cell.try_set("World".to_string());
155    /// assert!(result.is_err());
156    /// if let Err(value) = result {
157    ///     assert_eq!(value, "World".to_string());
158    /// }
159    ///
160    /// // The value remains unchanged
161    /// assert_eq!(cell.get(), Some(&"Hello".to_string()));
162    /// ```
163    pub fn try_set(&self, value: T) -> Result<(), T> {
164        // Initially, `is_some` is `Uninit`, so it's impossible to observe anything else without a
165        // prior `set`. Therefore, we will never panic if `set` was never called.
166        match self.state.compare_exchange(
167            WriteOnceState::Uninit as u8,
168            WriteOnceState::Writing as u8,
169            Ordering::Relaxed,
170            Ordering::Relaxed,
171        ) {
172            Ok(_) => {
173                unsafe { self.value.get().write(MaybeUninit::new(value)) }
174                // This store creates a happens-before relationship with the load in `get`
175                self.state
176                    .store(WriteOnceState::Init as u8, Ordering::Release);
177                Ok(())
178            }
179            Err(_) => Err(value),
180        }
181    }
182
183    /// Gets the value of the `WriteOnce`.
184    ///
185    /// Returns `Some(&T)` if the cell has a value, or `None` if it doesn't.
186    ///
187    /// # Examples
188    ///
189    /// ```
190    /// use once::write_once::WriteOnce;
191    ///
192    /// let cell = WriteOnce::<i32>::none();
193    /// assert_eq!(cell.get(), None);
194    ///
195    /// cell.set(42);
196    /// assert_eq!(cell.get(), Some(&42));
197    /// ```
198    pub fn get(&self) -> Option<&T> {
199        if self.is_set() {
200            // Safety: the value is initialized
201            let value = unsafe { (*self.value.get()).assume_init_ref() };
202            Some(value)
203        } else {
204            None
205        }
206    }
207
208    /// # Safety
209    ///
210    /// This method is unsafe because it returns a reference to the value without checking if the
211    /// value is initialized. The caller must ensure that the value is initialized before calling
212    /// this method.
213    pub unsafe fn get_unchecked(&self) -> &T {
214        // Safety: by assumption
215        unsafe { (*self.value.get()).assume_init_ref() }
216    }
217
218    /// Checks if the `WriteOnce` has a value.
219    ///
220    /// Returns `true` if the cell has a value, or `false` if it doesn't.
221    ///
222    /// This method only returns `true` if the value has been set and is ready to be read. In
223    /// particular, it will return `false` if the value is currently being set by another thread.
224    /// This may be relevant if the value is a large object and moving it in memory is expensive.
225    ///
226    /// # Examples
227    ///
228    /// ```
229    /// use once::write_once::WriteOnce;
230    ///
231    /// let cell = WriteOnce::<i32>::none();
232    /// assert!(!cell.is_set());
233    ///
234    /// cell.set(42);
235    /// assert!(cell.is_set());
236    /// ```
237    pub fn is_set(&self) -> bool {
238        self.state.load(Ordering::Acquire) == WriteOnceState::Init as u8
239    }
240
241    /// Gets a mutable reference to the value of the `WriteOnce`.
242    ///
243    /// Returns `Some(&mut T)` if the cell has a value, or `None` if it doesn't.
244    /// This method requires a mutable reference to the `WriteOnce`, which ensures
245    /// that no other thread is accessing the value.
246    ///
247    /// # Examples
248    ///
249    /// ```
250    /// use once::write_once::WriteOnce;
251    ///
252    /// let mut cell = WriteOnce::<String>::none();
253    /// assert_eq!(cell.get_mut(), None);
254    ///
255    /// cell.set("Hello".to_string());
256    ///
257    /// // Modify the value through a mutable reference
258    /// if let Some(value) = cell.get_mut() {
259    ///     value.push_str(", world!");
260    /// }
261    ///
262    /// assert_eq!(cell.get(), Some(&"Hello, world!".to_string()));
263    /// ```
264    pub fn get_mut(&mut self) -> Option<&mut T> {
265        if self.state.get_by_mut() == WriteOnceState::Init as u8 {
266            // Safety: the value is initialized
267            let value = unsafe { (*self.value.get()).assume_init_mut() };
268            Some(value)
269        } else {
270            None
271        }
272    }
273}
274
275impl<T> Drop for WriteOnce<T> {
276    fn drop(&mut self) {
277        // We have an exclusive reference to `self`, so we know that no other thread is accessing
278        // it. Moreover, we also have a happens-before relationship with all other operations on
279        // this `WriteOnce`, including a possible `set` that initialized the value. Therefore, the
280        // following code will never lead to a memory leak.
281        if self.state.get_by_mut() == WriteOnceState::Init as u8 {
282            // Safety: the value is initialized
283            unsafe { self.value.get_mut().assume_init_drop() };
284        }
285    }
286}
287
288impl<T: Clone> Clone for WriteOnce<T> {
289    fn clone(&self) -> Self {
290        if let Some(value) = self.get() {
291            Self::new(value.clone())
292        } else {
293            Self::none()
294        }
295    }
296}
297
298// We implement the other standard traits by pretending that we are `Option<T>`.
299
300impl<T: std::fmt::Debug> std::fmt::Debug for WriteOnce<T> {
301    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
302        write!(f, "{:?}", self.get())
303    }
304}
305
306impl<T: PartialEq> PartialEq for WriteOnce<T> {
307    fn eq(&self, other: &Self) -> bool {
308        self.get() == other.get()
309    }
310}
311
312impl<T: Eq> Eq for WriteOnce<T> {}
313
314impl<T: PartialOrd> PartialOrd for WriteOnce<T> {
315    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
316        self.get().partial_cmp(&other.get())
317    }
318}
319
320impl<T: Ord> Ord for WriteOnce<T> {
321    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
322        self.get().cmp(&other.get())
323    }
324}
325
326impl<T: std::hash::Hash> std::hash::Hash for WriteOnce<T> {
327    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
328        self.get().hash(state)
329    }
330}
331
332unsafe impl<T: Send> Send for WriteOnce<T> {}
333unsafe impl<T: Sync> Sync for WriteOnce<T> {}
334
335/// The possible states of a `WriteOnce`.
336///
337/// We distinguish between `Uninit` and `Writing` so that we reach the `Err` branch of `set` if
338/// `set` has been called by any thread before.
339///
340/// We distinguish between `Writing` and `Init` so that loading `Init` has a happens-before
341/// relationship with the write in `set`.
342#[repr(u8)]
343enum WriteOnceState {
344    Uninit = 0,
345    Writing = 1,
346    Init = 2,
347}
348
349#[cfg(test)]
350mod tests {
351    use std::{sync::Arc, thread};
352
353    use super::*;
354
355    #[test]
356    fn test_basic_functionality() {
357        // Test creating a new WriteOnce
358        let cell = WriteOnce::<i32>::none();
359        assert!(!cell.is_set());
360        assert_eq!(cell.get(), None);
361
362        // Test setting a value
363        cell.set(42);
364        assert!(cell.is_set());
365        assert_eq!(cell.get(), Some(&42));
366
367        // Test that try_set returns an error when the cell already has a value
368        let result = cell.try_set(100);
369        assert!(result.is_err());
370        if let Err(value) = result {
371            assert_eq!(value, 100);
372        }
373
374        // Test that the value remains unchanged
375        assert_eq!(cell.get(), Some(&42));
376    }
377
378    #[test]
379    fn test_get_mut() {
380        // Test get_mut with no value
381        let mut cell = WriteOnce::<String>::none();
382        assert_eq!(cell.get_mut(), None);
383
384        // Test get_mut with a value
385        cell.set("Hello".to_string());
386        {
387            let value = cell.get_mut().unwrap();
388            value.push_str(", world!");
389        }
390        assert_eq!(cell.get(), Some(&"Hello, world!".to_string()));
391    }
392
393    #[test]
394    #[should_panic(expected = "WriteOnce already set")]
395    fn test_set_panics_when_already_set() {
396        let cell = WriteOnce::<i32>::none();
397        cell.set(42);
398        cell.set(100); // This should panic
399    }
400
401    #[test]
402    fn test_drop_behavior() {
403        use std::sync::atomic::{AtomicUsize, Ordering};
404        static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
405
406        struct DropCounter;
407        impl Drop for DropCounter {
408            fn drop(&mut self) {
409                DROP_COUNT.fetch_add(1, Ordering::Relaxed);
410            }
411        }
412
413        // Test that the value is dropped when the WriteOnce is dropped
414        {
415            let cell = WriteOnce::<DropCounter>::none();
416            cell.set(DropCounter);
417            assert_eq!(DROP_COUNT.load(Ordering::Relaxed), 0);
418        }
419        assert_eq!(DROP_COUNT.load(Ordering::Relaxed), 1);
420
421        // Test that the value is not dropped if it was never set
422        {
423            let _cell = WriteOnce::<DropCounter>::none();
424        }
425        assert_eq!(DROP_COUNT.load(Ordering::Relaxed), 1); // Still 1
426    }
427
428    #[test]
429    fn test_thread_safety() {
430        let cell = Arc::new(WriteOnce::<i32>::none());
431        let cell_clone = Arc::clone(&cell);
432
433        // Spawn a thread that sets the value
434        let thread = thread::spawn(move || {
435            cell_clone.set(42);
436        });
437
438        // Wait for the thread to complete
439        thread.join().unwrap();
440
441        // Check that the value was set
442        assert!(cell.is_set());
443        assert_eq!(cell.get(), Some(&42));
444    }
445
446    #[test]
447    fn test_concurrent_set() {
448        let cell = Arc::new(WriteOnce::<i32>::none());
449        let mut handles = Vec::new();
450
451        // Spawn 10 threads that all try to set the value
452        for i in 0..10 {
453            let cell_clone = Arc::clone(&cell);
454            let handle = thread::spawn(move || {
455                let _ = cell_clone.try_set(i);
456            });
457            handles.push(handle);
458        }
459
460        // Wait for all threads to complete
461        for handle in handles {
462            handle.join().unwrap();
463        }
464
465        // Check that the value was set exactly once
466        assert!(cell.is_set());
467        let value = cell.get().unwrap();
468        assert!(*value >= 0 && *value < 10);
469    }
470
471    #[cfg(loom)]
472    mod loom_tests {
473        use super::*;
474        use crate::std_or_loom::{sync::Arc, thread};
475
476        #[test]
477        fn loom_concurrent_set_and_get() {
478            loom::model(|| {
479                let cell = Arc::new(WriteOnce::<i32>::none());
480
481                // Thread 1: Try to set the value
482                let cell1 = Arc::clone(&cell);
483                let t1 = thread::spawn(move || {
484                    let _ = cell1.try_set(42);
485                });
486
487                // Thread 2: Try to set the value
488                let cell2 = Arc::clone(&cell);
489                let t2 = thread::spawn(move || {
490                    let _ = cell2.try_set(100);
491                });
492
493                // Thread 3: Get the value
494                let cell3 = Arc::clone(&cell);
495                let t3 = thread::spawn(move || {
496                    let _ = cell3.get();
497                });
498
499                t1.join().unwrap();
500                t2.join().unwrap();
501                t3.join().unwrap();
502
503                // The value should be either 42 or 100, depending on which thread won the race
504                assert!(cell.is_set());
505                let value = cell.get().unwrap();
506                assert!(*value == 42 || *value == 100);
507            });
508        }
509
510        #[test]
511        fn loom_is_set_during_write() {
512            loom::model(|| {
513                let cell = Arc::new(WriteOnce::<i32>::none());
514
515                // Thread 1: Set the value
516                let cell1 = Arc::clone(&cell);
517                let t1 = thread::spawn(move || {
518                    // This will set the state to Writing and then to Init
519                    cell1.set(42);
520                });
521
522                // Thread 2: Check if the value is set
523                let cell2 = Arc::clone(&cell);
524                let t2 = thread::spawn(move || {
525                    // This may observe any of the three states
526                    let is_set = cell2.is_set();
527                    if is_set {
528                        // If is_set returns true, get() should return Some
529                        assert!(cell2.get().is_some());
530                    }
531                });
532
533                t1.join().unwrap();
534                t2.join().unwrap();
535
536                // After both threads complete, the value should be set
537                assert!(cell.is_set());
538                assert_eq!(cell.get(), Some(&42));
539            });
540        }
541    }
542}