Skip to main content

aria_primitives/
slot.rs

1//! A build-once cell for a value whose construction can fail.
2
3use std::sync::{Mutex, OnceLock};
4
5/// Losers of the build race wait, and a failure leaves the slot empty for the
6/// next caller to retry.
7pub 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}