Skip to content

Commit

Permalink
Handle multiple initializations
Browse files Browse the repository at this point in the history
  • Loading branch information
equation314 committed Jul 18, 2024
1 parent b2927b0 commit 8c6cce5
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 47 deletions.
46 changes: 36 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,52 @@

Initialize a static value lazily.

Unlike [`lazy_static`][1], this crate does not provide concurrency safety.
The value **MUST** be used after only **ONE** initialization. However, it
can be more efficient, as there is no need to check whether other threads
are also performing initialization at the same time.
Unlike [`lazy_static`][1], which hardcodes the initialization routine in a macro, you can initialize the value in any way.

[1]: https://docs.rs/lazy_static

## Examples

```rust
use lazyinit::LazyInit;

static VALUE: LazyInit<u32> = LazyInit::new();
assert!(!VALUE.is_init());
assert!(!VALUE.is_inited());
// println!("{}", *VALUE); // panic: use uninitialized value
assert_eq!(VALUE.try_get(), None);
assert_eq!(VALUE.get(), None);

VALUE.init_by(233);
VALUE.init_once(233);
// VALUE.init_by(666); // panic: already initialized
assert!(VALUE.is_init());
assert!(VALUE.is_inited());
assert_eq!(*VALUE, 233);
assert_eq!(VALUE.try_get(), Some(&233));
assert_eq!(VALUE.get(), Some(&233));
```

[1]: https://docs.rs/lazy_static/latest/lazy_static/
Only one of the multiple initializations can succeed:

```rust
use lazyinit::LazyInit;
use std::time::Duration;

const N: usize = 16;
static VALUE: LazyInit<usize> = LazyInit::new();

let threads = (0..N)
.map(|i| {
std::thread::spawn(move || {
std::thread::sleep(Duration::from_millis(10));
VALUE.init_once(i)
})
})
.collect::<Vec<_>>();

let mut ok = 0;
for (i, thread) in threads.into_iter().enumerate() {
if thread.join().unwrap().is_some() {
ok += 1;
assert_eq!(*VALUE, i);
}
}

assert_eq!(ok, 1);
```
102 changes: 65 additions & 37 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#![no_std]
#![cfg_attr(not(test), no_std)]
#![doc = include_str!("../README.md")]

use core::cell::UnsafeCell;
Expand Down Expand Up @@ -28,62 +28,58 @@ impl<T> LazyInit<T> {
}
}

/// Initializes the value.
/// Initializes the value once and only once.
///
/// # Panics
///
/// Panics if the value is already initialized.
pub fn init_by(&self, data: T) {
assert!(!self.is_init());
unsafe { (*self.data.get()).as_mut_ptr().write(data) };
self.inited.store(true, Ordering::Release);
/// Returns [`None`] if the value is already initialized.
pub fn init_once(&self, data: T) -> Option<&T> {
match self
.inited
.compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed)
{
Ok(_) => {
unsafe { (*self.data.get()).as_mut_ptr().write(data) };
Some(unsafe { self.force_get() })
}
Err(_) => None,
}
}

/// Checks whether the value is initialized.
pub fn is_init(&self) -> bool {
pub fn is_inited(&self) -> bool {
self.inited.load(Ordering::Acquire)
}

/// Gets a reference to the value.
///
/// Returns [`None`] if the value is not initialized.
pub fn try_get(&self) -> Option<&T> {
if self.is_init() {
unsafe { Some(&*(*self.data.get()).as_ptr()) }
pub fn get(&self) -> Option<&T> {
if self.is_inited() {
Some(unsafe { self.force_get() })
} else {
None
}
}

fn check_init(&self) {
if !self.is_init() {
panic!(
"Use uninitialized value: {:?}",
core::any::type_name::<Self>()
)
/// Gets a mutable reference to the value.
///
/// Returns [`None`] if the value is not initialized.
pub fn get_mut(&mut self) -> Option<&mut T> {
if self.is_inited() {
Some(unsafe { self.force_get_mut() })
} else {
None
}
}

#[inline]
fn get(&self) -> &T {
self.check_init();
unsafe { self.get_unchecked() }
}

#[inline]
fn get_mut(&mut self) -> &mut T {
self.check_init();
unsafe { self.get_mut_unchecked() }
}

/// Gets the reference to the value without checking if it is initialized.
///
/// # Safety
///
/// Must be called after initialization.
#[inline]
pub unsafe fn get_unchecked(&self) -> &T {
&*(*self.data.get()).as_ptr()
debug_assert!(self.is_inited());
self.force_get()
}

/// Get a mutable reference to the value without checking if it is initialized.
Expand All @@ -93,13 +89,31 @@ impl<T> LazyInit<T> {
/// Must be called after initialization.
#[inline]
pub unsafe fn get_mut_unchecked(&mut self) -> &mut T {
&mut *(*self.data.get()).as_mut_ptr()
debug_assert!(self.is_inited());
self.force_get_mut()
}

#[inline]
unsafe fn force_get(&self) -> &T {
(*self.data.get()).assume_init_ref()
}

#[inline]
unsafe fn force_get_mut(&mut self) -> &mut T {
(*self.data.get()).assume_init_mut()
}

fn panic_message(&self) -> ! {
panic!(
"Use uninitialized value: {:?}",
core::any::type_name::<Self>()
)
}
}

impl<T: fmt::Debug> fmt::Debug for LazyInit<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.try_get() {
match self.get() {
Some(s) => write!(f, "LazyInit {{ data: ")
.and_then(|()| s.fmt(f))
.and_then(|()| write!(f, "}}")),
Expand All @@ -108,24 +122,38 @@ impl<T: fmt::Debug> fmt::Debug for LazyInit<T> {
}
}

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

impl<T> Deref for LazyInit<T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
self.get()
if self.is_inited() {
unsafe { self.force_get() }
} else {
self.panic_message()
}
}
}

impl<T> DerefMut for LazyInit<T> {
#[inline]
fn deref_mut(&mut self) -> &mut T {
self.get_mut()
if self.is_inited() {
unsafe { self.force_get_mut() }
} else {
self.panic_message()
}
}
}

impl<T> Drop for LazyInit<T> {
fn drop(&mut self) {
if self.is_init() {
if self.is_inited() {
unsafe { core::ptr::drop_in_place((*self.data.get()).as_mut_ptr()) };
}
}
Expand Down

0 comments on commit 8c6cce5

Please sign in to comment.