#![doc(html_root_url = "https://docs.rs/try-lazy-init/0.0.2")]
#![warn(clippy::pedantic)]
#![allow(clippy::semicolon_if_nothing_returned)]
#![deny(missing_docs)]
#[cfg(doctest)]
pub mod readme {
doc_comment::doctest!("../README.md");
}
use std::{
cell::UnsafeCell,
fmt,
sync::{
atomic::{AtomicBool, Ordering},
Mutex,
},
};
#[derive(Clone)]
enum ThisOrThat<T, U> {
This(T),
That(U),
}
pub struct LazyTransform<T, U> {
initialized: AtomicBool,
lock: Mutex<()>,
value: UnsafeCell<Option<ThisOrThat<T, U>>>,
}
impl<T, U> LazyTransform<T, U> {
fn extract(&self) -> Option<&U> {
match unsafe { (*self.value.get()).as_ref() } {
None => None,
Some(&ThisOrThat::This(_)) => panic!(), Some(&ThisOrThat::That(ref that)) => Some(that),
}
}
}
impl<T, U> LazyTransform<T, U> {
pub fn new(t: T) -> LazyTransform<T, U> {
LazyTransform {
initialized: AtomicBool::new(false),
lock: Mutex::new(()),
value: UnsafeCell::new(Some(ThisOrThat::This(t))),
}
}
pub fn into_inner(self) -> Result<U, T> {
match self.value.into_inner().unwrap() {
ThisOrThat::This(t) => Err(t),
ThisOrThat::That(u) => Ok(u),
}
}
pub fn try_into_inner(self) -> Result<U, Option<T>> {
match self.value.into_inner() {
None => Err(None),
Some(ThisOrThat::This(t)) => Err(Some(t)),
Some(ThisOrThat::That(u)) => Ok(u),
}
}
}
impl<T, U> LazyTransform<T, U> {
pub fn get_or_create<F>(&self, f: F) -> &U
where
F: FnOnce(T) -> U,
{
if !self.initialized.load(Ordering::Acquire) {
let _lock = self.lock.lock().unwrap();
#[allow(clippy::if_not_else)]
if !self.initialized.load(Ordering::Relaxed) {
let value = unsafe { &mut *self.value.get() };
let this = match value.take().unwrap() {
ThisOrThat::This(t) => t,
ThisOrThat::That(_) => panic!(), };
*value = Some(ThisOrThat::That(f(this)));
self.initialized.store(true, Ordering::Release);
} else {
}
}
self.extract().unwrap()
}
pub fn try_get_or_create<F, E>(&self, f: F) -> Result<&U, E>
where
T: Clone,
F: FnOnce(T) -> Result<U, E>,
{
#[allow(clippy::if_not_else)]
if !self.initialized.load(Ordering::Acquire) {
let _lock = self.lock.lock().unwrap();
if !self.initialized.load(Ordering::Relaxed) {
let value = unsafe { &mut *self.value.get() };
let this = match value.as_ref().unwrap() {
ThisOrThat::This(t) => t.clone(),
ThisOrThat::That(_) => panic!(), };
*value = Some(ThisOrThat::That(f(this)?));
self.initialized.store(true, Ordering::Release);
} else {
}
}
Ok(self.extract().unwrap())
}
pub fn get_or_create_or_poison<F, E>(&self, f: F) -> Result<&U, Option<E>>
where
F: FnOnce(T) -> Result<U, E>,
{
#[allow(clippy::if_not_else)]
if !self.initialized.load(Ordering::Acquire) {
let _lock = self.lock.lock().unwrap();
if !self.initialized.load(Ordering::Relaxed) {
let value = unsafe { &mut *self.value.get() };
let this = match value.take() {
None => return Err(None), Some(ThisOrThat::This(t)) => t,
Some(ThisOrThat::That(_)) => panic!(), };
*value = Some(ThisOrThat::That(f(this)?));
self.initialized.store(true, Ordering::Release);
} else {
}
}
Ok(self.extract().unwrap())
}
pub fn get(&self) -> Option<&U> {
if self.initialized.load(Ordering::Acquire) {
self.extract()
} else {
None
}
}
}
unsafe impl<T, U> Sync for LazyTransform<T, U>
where
T: Send,
U: Send + Sync,
{
}
impl<T, U> Clone for LazyTransform<T, U>
where
T: Clone,
U: Clone,
{
fn clone(&self) -> Self {
if self.initialized.load(Ordering::Acquire) {
Self {
initialized: true.into(),
lock: Mutex::default(),
value: UnsafeCell::new(unsafe {
(&*self.value.get()).clone()
}),
}
} else {
let _lock = self.lock.lock().unwrap();
Self {
initialized: self.initialized.load(Ordering::Relaxed).into(),
lock: Mutex::default(),
value: UnsafeCell::new(unsafe {
(&*self.value.get()).clone()
}),
}
}
}
fn clone_from(&mut self, source: &Self) {
if self.initialized.load(Ordering::Acquire) {
unsafe {
*self.value.get() = (&*source.value.get()).clone();
self.initialized.store(true, Ordering::Release);
}
} else {
let _lock = source.lock.lock().unwrap();
unsafe {
*self.value.get() = (&*source.value.get()).clone();
self.initialized.store(
source.initialized.load(Ordering::Relaxed),
Ordering::Relaxed,
);
}
}
}
}
impl<T, U> Default for LazyTransform<T, U>
where
T: Default,
{
fn default() -> Self {
LazyTransform::new(T::default())
}
}
#[derive(Clone)]
pub struct Lazy<T> {
inner: LazyTransform<(), T>,
}
impl<T> Lazy<T> {
#[must_use]
pub fn new() -> Lazy<T> {
Self::default()
}
pub fn into_inner(self) -> Option<T> {
self.inner.into_inner().ok()
}
}
impl<T> Lazy<T> {
pub fn get_or_create<F>(&self, f: F) -> &T
where
F: FnOnce() -> T,
{
self.inner.get_or_create(|_| f())
}
pub fn try_get_or_create<F, E>(&self, f: F) -> Result<&T, E>
where
F: FnOnce() -> Result<T, E>,
{
self.inner.try_get_or_create(|_| f())
}
pub fn get(&self) -> Option<&T> {
self.inner.get()
}
}
impl<T> Default for Lazy<T> {
fn default() -> Self {
Lazy {
inner: LazyTransform::new(()),
}
}
}
impl<T> fmt::Debug for Lazy<T>
where
T: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(v) = self.get() {
f.write_fmt(format_args!("Lazy({:?})", v))
} else {
f.write_str("Lazy(<uninitialized>)")
}
}
}
#[cfg(test)]
extern crate scoped_pool;
#[cfg(test)]
mod tests {
use super::{Lazy, LazyTransform};
use scoped_pool::Pool;
use std::{
sync::atomic::{AtomicUsize, Ordering},
thread, time,
};
#[test]
fn test_lazy() {
let lazy_value: Lazy<u8> = Lazy::new();
assert_eq!(lazy_value.get(), None);
let n = AtomicUsize::new(0);
let pool = Pool::new(100);
pool.scoped(|scope| {
for _ in 0..100 {
let lazy_ref = &lazy_value;
let n_ref = &n;
scope.execute(move || {
let ten_millis = time::Duration::from_millis(10);
thread::sleep(ten_millis);
let value = *lazy_ref.get_or_create(|| {
thread::sleep(ten_millis);
n_ref.fetch_add(1, Ordering::Relaxed);
42
});
assert_eq!(value, 42);
let value = lazy_ref.get();
assert_eq!(value, Some(&42));
});
}
});
assert_eq!(n.load(Ordering::SeqCst), 1);
}
#[test]
fn test_lazy_fallible() {
let lazy_value: Lazy<u8> = Lazy::new();
lazy_value.try_get_or_create(|| Err(())).unwrap_err();
assert_eq!(lazy_value.get(), None);
let n = AtomicUsize::new(0);
let pool = Pool::new(100);
pool.scoped(|scope| {
for _ in 0..100 {
let lazy_ref = &lazy_value;
let n_ref = &n;
scope.execute(move || {
let ten_millis = time::Duration::from_millis(10);
thread::sleep(ten_millis);
let value = *lazy_ref
.try_get_or_create(|| {
thread::sleep(ten_millis);
n_ref.fetch_add(1, Ordering::Relaxed);
Result::<_, ()>::Ok(42)
})
.unwrap();
assert_eq!(value, 42);
let value = lazy_ref.get();
assert_eq!(value, Some(&42));
});
}
});
assert_eq!(n.load(Ordering::SeqCst), 1);
}
#[test]
fn test_lazy_transform() {
let lazy_value: LazyTransform<u8, u8> = LazyTransform::new(21);
assert_eq!(lazy_value.get(), None);
let n = AtomicUsize::new(0);
let pool = Pool::new(100);
pool.scoped(|scope| {
for _ in 0..100 {
let lazy_ref = &lazy_value;
let n_ref = &n;
scope.execute(move || {
let ten_millis = time::Duration::from_millis(10);
thread::sleep(ten_millis);
let value = *lazy_ref.get_or_create(|v| {
thread::sleep(ten_millis);
n_ref.fetch_add(1, Ordering::Relaxed);
v * 2
});
assert_eq!(value, 42);
let value = lazy_ref.get();
assert_eq!(value, Some(&42));
});
}
});
assert_eq!(n.load(Ordering::SeqCst), 1);
}
#[test]
fn test_lazy_transform_fallible() {
let lazy_value: LazyTransform<u8, u8> = LazyTransform::new(21);
lazy_value.try_get_or_create(|_| Err(())).unwrap_err();
assert_eq!(lazy_value.get(), None);
let n = AtomicUsize::new(0);
let pool = Pool::new(100);
pool.scoped(|scope| {
for _ in 0..100 {
let lazy_ref = &lazy_value;
let n_ref = &n;
scope.execute(move || {
let ten_millis = time::Duration::from_millis(10);
thread::sleep(ten_millis);
let value = *lazy_ref
.try_get_or_create(|v| {
thread::sleep(ten_millis);
n_ref.fetch_add(1, Ordering::Relaxed);
Result::<_, ()>::Ok(v * 2)
})
.unwrap();
assert_eq!(value, 42);
let value = lazy_ref.get();
assert_eq!(value, Some(&42));
});
}
});
assert_eq!(n.load(Ordering::SeqCst), 1);
}
}