tracing_mutex/stdsync/tracing/
lazy_lock.rs

1//! Wrapper implementation for LazyLock
2//!
3//! This lives in a separate module as LazyLock would otherwise raise our MSRV to 1.80. Reevaluate
4//! this in the future.
5use std::fmt;
6use std::fmt::Debug;
7use std::ops::Deref;
8
9use crate::LazyMutexId;
10
11/// Wrapper for [`std::sync::LazyLock`]
12///
13/// This wrapper participates in cycle detection like all other primitives in this crate. It should
14/// only be possible to encounter cycles when acquiring mutexes in the initialisation function.
15///
16/// # Examples
17///
18/// ```
19/// use tracing_mutex::stdsync::tracing::LazyLock;
20///
21/// static LOCK: LazyLock<i32> = LazyLock::new(|| {
22///     println!("Hello, world!");
23///     42
24/// });
25///
26/// // This should print "Hello, world!"
27/// println!("{}", *LOCK);
28/// // This should not.
29/// println!("{}", *LOCK);
30/// ```
31pub struct LazyLock<T, F = fn() -> T> {
32    // MSRV violation is fine, this is gated behind a cfg! check
33    #[allow(clippy::incompatible_msrv)]
34    inner: std::sync::LazyLock<T, F>,
35    id: LazyMutexId,
36}
37
38impl<T, F: FnOnce() -> T> LazyLock<T, F> {
39    /// Creates a new lazy value with the given initializing function.
40    pub const fn new(f: F) -> LazyLock<T, F> {
41        Self {
42            id: LazyMutexId::new(),
43            // MSRV violation is fine, this is gated behind a cfg! check
44            #[allow(clippy::incompatible_msrv)]
45            inner: std::sync::LazyLock::new(f),
46        }
47    }
48
49    /// Force this lazy lock to be evaluated.
50    ///
51    /// This is equivalent to dereferencing, but is more explicit.
52    pub fn force(this: &LazyLock<T, F>) -> &T {
53        this
54    }
55}
56
57impl<T, F: FnOnce() -> T> Deref for LazyLock<T, F> {
58    type Target = T;
59
60    fn deref(&self) -> &Self::Target {
61        self.id.with_held(|| &*self.inner)
62    }
63}
64
65impl<T: Default> Default for LazyLock<T> {
66    /// Return a `LazyLock` that is initialized through [`Default`].
67    fn default() -> Self {
68        Self::new(Default::default)
69    }
70}
71
72impl<T: Debug, F> Debug for LazyLock<T, F> {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        // Cannot implement this ourselves because the get() used is nightly, so delegate.
75        self.inner.fmt(f)
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use crate::stdsync::Mutex;
82
83    use super::*;
84
85    #[test]
86    fn test_only_init_once() {
87        let mut init_counter = 0;
88
89        let lock = LazyLock::new(|| {
90            init_counter += 1;
91            42
92        });
93
94        assert_eq!(*lock, 42);
95        LazyLock::force(&lock);
96
97        // Ensure we can access the init counter
98        drop(lock);
99
100        assert_eq!(init_counter, 1);
101    }
102
103    #[test]
104    #[should_panic(expected = "Found cycle")]
105    fn test_panic_with_cycle() {
106        let mutex = Mutex::new(());
107
108        let lock = LazyLock::new(|| *mutex.lock().unwrap());
109
110        // Establish the relation from lock to mutex
111        LazyLock::force(&lock);
112
113        // Now do it the other way around, which should crash
114        let _guard = mutex.lock().unwrap();
115        LazyLock::force(&lock);
116    }
117}