//! Wrapper implementation for LazyLock //! //! This lives in a separate module as LazyLock would otherwise raise our MSRV to 1.80. Reevaluate //! this in the future. use std::fmt; use std::fmt::Debug; use std::ops::Deref; use crate::LazyMutexId; /// Wrapper for [`std::sync::LazyLock`] /// /// This wrapper participates in cycle detection like all other primitives in this crate. It should /// only be possible to encounter cycles when acquiring mutexes in the initialisation function. /// /// # Examples /// /// ``` /// use tracing_mutex::stdsync::tracing::LazyLock; /// /// static LOCK: LazyLock = LazyLock::new(|| { /// println!("Hello, world!"); /// 42 /// }); /// /// // This should print "Hello, world!" /// println!("{}", *LOCK); /// // This should not. /// println!("{}", *LOCK); /// ``` pub struct LazyLock T> { // MSRV violation is fine, this is gated behind a cfg! check #[allow(clippy::incompatible_msrv)] inner: std::sync::LazyLock, id: LazyMutexId, } impl T> LazyLock { /// Creates a new lazy value with the given initializing function. pub const fn new(f: F) -> LazyLock { Self { id: LazyMutexId::new(), // MSRV violation is fine, this is gated behind a cfg! check #[allow(clippy::incompatible_msrv)] inner: std::sync::LazyLock::new(f), } } /// Force this lazy lock to be evaluated. /// /// This is equivalent to dereferencing, but is more explicit. pub fn force(this: &LazyLock) -> &T { this } } impl T> Deref for LazyLock { type Target = T; fn deref(&self) -> &Self::Target { self.id.with_held(|| &*self.inner) } } impl Default for LazyLock { /// Return a `LazyLock` that is initialized through [`Default`]. fn default() -> Self { Self::new(Default::default) } } impl Debug for LazyLock { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // Cannot implement this ourselves because the get() used is nightly, so delegate. self.inner.fmt(f) } } #[cfg(test)] mod tests { use crate::stdsync::Mutex; use super::*; #[test] fn test_only_init_once() { let mut init_counter = 0; let lock = LazyLock::new(|| { init_counter += 1; 42 }); assert_eq!(*lock, 42); LazyLock::force(&lock); // Ensure we can access the init counter drop(lock); assert_eq!(init_counter, 1); } #[test] #[should_panic(expected = "Found cycle")] fn test_panic_with_cycle() { let mutex = Mutex::new(()); let lock = LazyLock::new(|| *mutex.lock().unwrap()); // Establish the relation from lock to mutex LazyLock::force(&lock); // Now do it the other way around, which should crash let _guard = mutex.lock().unwrap(); LazyLock::force(&lock); } }