1use std::sync::{Mutex, OnceLock};
4
5pub struct Slot<T> {
8 initialization: Mutex<()>,
9 value: OnceLock<T>,
10}
11
12impl<T> Slot<T> {
13 pub fn empty() -> Self {
14 Self {
15 initialization: Mutex::new(()),
16 value: OnceLock::new(),
17 }
18 }
19
20 pub fn get(&self) -> Option<&T> {
21 self.value.get()
22 }
23
24 pub fn get_or_try_init<E>(&self, build: impl FnOnce() -> Result<T, E>) -> Result<&T, E> {
25 if let Some(value) = self.value.get() {
26 return Ok(value);
27 }
28 let _initialization = self
29 .initialization
30 .lock()
31 .unwrap_or_else(|poisoned| poisoned.into_inner());
32 if let Some(value) = self.value.get() {
33 return Ok(value);
34 }
35 let value = build()?;
36 Ok(self.value.get_or_init(|| value))
37 }
38}
39
40#[cfg(test)]
41mod tests {
42 use super::*;
43
44 #[test]
45 fn a_failed_build_leaves_the_slot_open_for_a_retry() {
46 let slot: Slot<u32> = Slot::empty();
47
48 let failed: Result<&u32, &str> = slot.get_or_try_init(|| Err("no model"));
49 assert_eq!(failed.unwrap_err(), "no model");
50
51 let built: Result<&u32, &str> = slot.get_or_try_init(|| Ok(7));
52 assert_eq!(built.unwrap(), &7);
53 }
54
55 #[test]
56 fn a_built_slot_never_builds_again() {
57 let slot: Slot<u32> = Slot::empty();
58 let _first: Result<&u32, &str> = slot.get_or_try_init(|| Ok(1));
59
60 let second: Result<&u32, &str> = slot.get_or_try_init(|| panic!("rebuilt a full slot"));
61 assert_eq!(second.unwrap(), &1);
62 }
63}