use std::ops::Deref;
use std::sync::OnceLock;
pub struct LazyObject<T> {
init: Box<dyn Fn() -> T + Send + Sync>,
value: OnceLock<T>,
}
impl<T: Send + Sync> LazyObject<T> {
#[must_use]
pub fn new(init: impl Fn() -> T + Send + Sync + 'static) -> Self {
Self {
init: Box::new(init),
value: OnceLock::new(),
}
}
#[must_use]
pub fn get(&self) -> &T {
self.value.get_or_init(|| (self.init)())
}
}
impl<T: Send + Sync> Deref for LazyObject<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.get()
}
}
#[must_use]
pub fn lazy<T: Send + Sync>(init: impl Fn() -> T + Send + Sync + 'static) -> LazyObject<T> {
LazyObject::new(init)
}
#[must_use]
pub fn partition<'a>(s: &'a str, sep: &str) -> (&'a str, &'a str, &'a str) {
assert!(!sep.is_empty(), "empty separator");
match s.find(sep) {
Some(index) => {
let tail_index = index + sep.len();
(&s[..index], &s[index..tail_index], &s[tail_index..])
}
None => (s, "", ""),
}
}
#[cfg(test)]
mod tests {
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
use std::thread;
use super::{LazyObject, lazy, partition};
fn assert_send_sync<T: Send + Sync>() {}
#[test]
fn test_lazy_object_defers_initialization_until_first_access() {
let calls = Arc::new(AtomicUsize::new(0));
let init_calls = Arc::clone(&calls);
let lazy_value = LazyObject::new(move || {
init_calls.fetch_add(1, Ordering::SeqCst);
String::from("ready")
});
assert_eq!(calls.load(Ordering::SeqCst), 0);
assert_eq!(lazy_value.get(), "ready");
assert_eq!(calls.load(Ordering::SeqCst), 1);
assert_eq!(lazy_value.get(), "ready");
assert_eq!(calls.load(Ordering::SeqCst), 1);
}
#[test]
fn test_lazy_object_is_thread_safe_and_initializes_once() {
let calls = Arc::new(AtomicUsize::new(0));
let init_calls = Arc::clone(&calls);
let lazy_value: Arc<LazyObject<usize>> = Arc::new(LazyObject::new(move || {
init_calls.fetch_add(1, Ordering::SeqCst);
42
}));
let handles: Vec<_> = (0..8)
.map(|_| {
let lazy_value = Arc::clone(&lazy_value);
thread::spawn(move || *lazy_value.get())
})
.collect();
let values: Vec<_> = handles
.into_iter()
.map(|handle| handle.join().expect("thread should finish"))
.collect();
assert_eq!(values, vec![42; 8]);
assert_eq!(calls.load(Ordering::SeqCst), 1);
}
#[test]
fn test_lazy_object_deref_forwards_access() {
let lazy_value = LazyObject::new(|| String::from("hello"));
assert_eq!(lazy_value.len(), 5);
assert_eq!(&*lazy_value, "hello");
}
#[test]
fn test_lazy_object_is_send_and_sync() {
assert_send_sync::<LazyObject<String>>();
}
#[test]
fn test_lazy_convenience_constructor() {
let lazy_value = lazy(|| 7_u32);
assert_eq!(*lazy_value.get(), 7);
}
#[test]
fn test_lazy_object() {
let calls = Arc::new(AtomicUsize::new(0));
let init_calls = Arc::clone(&calls);
let lazy_value = lazy(move || {
init_calls.fetch_add(1, Ordering::SeqCst);
vec![0, 1, 2]
});
assert_eq!(calls.load(Ordering::SeqCst), 0);
assert_eq!(lazy_value.len(), 3);
assert_eq!(lazy_value[1], 1);
assert_eq!(calls.load(Ordering::SeqCst), 1);
assert_eq!(lazy_value.first(), Some(&0));
assert_eq!(calls.load(Ordering::SeqCst), 1);
}
#[test]
fn test_partition() {
let cases = [
("alpha-beta-gamma", "-", ("alpha", "-", "beta-gamma")),
("alphabetagamma", "-", ("alphabetagamma", "", "")),
("", ":", ("", "", "")),
("key::value", "::", ("key", "::", "value")),
];
for (input, separator, expected) in cases {
assert_eq!(partition(input, separator), expected);
}
}
#[test]
fn test_partition_with_separator_present() {
assert_eq!(
partition("alpha-beta-gamma", "-"),
("alpha", "-", "beta-gamma")
);
}
#[test]
fn test_partition_without_separator_present() {
assert_eq!(partition("alphabetagamma", "-"), ("alphabetagamma", "", ""));
}
#[test]
fn test_partition_with_empty_input() {
assert_eq!(partition("", ":"), ("", "", ""));
}
#[test]
#[should_panic(expected = "empty separator")]
fn test_partition_rejects_empty_separator() {
let _ = partition("abc", "");
}
}