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}