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}