//! Define `ConfigValue` & helpers use std::fmt::Display; use std::ops::Deref; use std::pin::Pin; use std::sync::OnceLock; /// A struct which acts like a `OnceLock` but with async support pub struct ConfigValue { inner: Inner, } enum Inner { Sync { lock: OnceLock, func: fn() -> T, }, Async { lock: OnceLock, func: fn() -> Pin>>, }, } impl ConfigValue { /// create a new synchronous ConfigValue /// /// this is the same as a OnceLock basically pub const fn new_sync(func: fn() -> T) -> ConfigValue { ConfigValue { inner: Inner::Sync { lock: OnceLock::new(), func, }, } } /// create a new async based ConfigValue pub const fn new_async(func: fn() -> Pin>>) -> ConfigValue { ConfigValue { inner: Inner::Async { lock: OnceLock::new(), func, }, } } /// get the value. if the value is uninitialized, initialize it pub async fn get(&self) -> &T { match &self.inner { Inner::Sync { lock, func } => lock.get_or_init(func), Inner::Async { lock, func } => { if let Some(val) = lock.get() { return val; } let res = func().await; let _ = lock.set(res); lock.wait() } } } /// same as `get` but discards the result /// this should be called in main to make sure that values are properly initialized before deref pub async fn init(&self) { let _ = self.get().await; } } impl Deref for ConfigValue { type Target = T; fn deref(&self) -> &Self::Target { match &self.inner { Inner::Sync { lock, func: _ } => lock.wait(), Inner::Async { lock, func: _ } => lock.wait(), } } } impl Display for ConfigValue where T: Display, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.deref().fmt(f) } } #[macro_export] /// initializes all values passed in. useful to batch init without tons of .init values macro_rules! init { ( $( $x:expr ),* ) => { $( $x.init().await; )* }; }