manually_static/stack_and_ref.rs
1use std::ops::Deref;
2#[cfg(debug_assertions)]
3use std::sync::atomic::{AtomicBool, Ordering};
4#[cfg(debug_assertions)]
5use std::sync::Arc;
6
7/// `ManuallyStatic<T>` holds a value `T` and provides references to it
8/// with checking with `debug_assertions`.
9///
10/// In debug builds, it tracks if the original `ManuallyStatic` instance
11/// has been dropped, causing any derived `ManuallyStaticRef`
12/// to panic upon dereference if the original
13/// owner is no longer alive.
14///
15/// This is useful for simulating a 'static lifetime where you want
16/// runtime checks for use-after-free in debug environments.
17///
18/// # Example
19///
20/// ```rust
21/// use manually_static::ManuallyStatic;
22/// use std::thread;
23/// use std::time::Duration;
24///
25/// struct AppConfig {
26/// version: String,
27/// }
28///
29/// let config = ManuallyStatic::new(AppConfig {
30/// version: String::from("1.0.0"),
31/// });
32///
33/// // Get a 'static reference to the config.
34/// // This is where ManuallyStatic shines, allowing us to pass
35/// // a reference that the compiler would normally complain about
36/// // without complex ownership transfers or Arc for simple reads.
37/// let config_ref = config.get_ref();
38///
39/// let handle = thread::spawn(move || {
40/// // In this thread, we can safely access the config via the 'static reference.
41/// // In debug builds, if `config` (the original ManuallyStatic) was dropped
42/// // before this thread accessed it, it would panic.
43///
44/// thread::sleep(Duration::from_millis(100)); // Simulate some work
45///
46/// println!("Thread: App Version: {}", config_ref.version);
47/// });
48///
49/// handle.join().unwrap();
50///
51/// // config is dropped here after the thread has finished
52/// ```
53pub struct ManuallyStatic<T> {
54 value: T,
55 /// This flag is only present in debug builds (`cfg(debug_assertions)`).
56 /// It is set to `true` when the `ManuallyStatic` instance is dropped.
57 #[cfg(debug_assertions)]
58 was_dropped: Arc<AtomicBool>,
59}
60
61impl<T> ManuallyStatic<T> {
62 /// Creates a new `ManuallyStatic` instance holding the given value.
63 pub fn new(value: T) -> Self {
64 Self {
65 value,
66 #[cfg(debug_assertions)]
67 was_dropped: Arc::new(AtomicBool::new(false)),
68 }
69 }
70
71 /// Returns a [`ManuallyStaticRef`] that provides immutable access to the
72 /// contained value. In debug builds, dereferencing this will panic
73 /// if the original `ManuallyStatic` instance has been dropped.
74 pub fn get_ref(&self) -> ManuallyStaticRef<T> {
75 ManuallyStaticRef {
76 value_ref: &self.value,
77 #[cfg(debug_assertions)]
78 was_dropped: self.was_dropped.clone(),
79 }
80 }
81}
82
83/// Implements the `Drop` trait for [`ManuallyStatic<T>`] only in debug builds.
84/// When [`ManuallyStatic`] is dropped, it sets the `was_dropped` flag to `true`.
85#[cfg(debug_assertions)]
86impl<T> Drop for ManuallyStatic<T> {
87 fn drop(&mut self) {
88 self.was_dropped.store(true, Ordering::Release);
89 }
90}
91
92/// A reference to the value held by [`ManuallyStatic<T>`].
93/// In debug builds, it will panic if dereferenced after the
94/// original [`ManuallyStatic`] has been dropped.
95pub struct ManuallyStaticRef<T> {
96 value_ref: *const T,
97 #[cfg(debug_assertions)]
98 was_dropped: Arc<AtomicBool>,
99}
100
101impl<T> Deref for ManuallyStaticRef<T> {
102 type Target = T;
103
104 fn deref(&self) -> &T {
105 #[cfg(debug_assertions)]
106 {
107 assert!(
108 !self.was_dropped.load(Ordering::Acquire),
109 "Attempted to dereference ManuallyStaticRef after ManuallyStatic was dropped!"
110 );
111 }
112
113 unsafe { &*self.value_ref }
114 }
115}
116
117unsafe impl<T: Send> Send for ManuallyStaticRef<T> {}
118unsafe impl<T: Sync> Sync for ManuallyStaticRef<T> {}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn test_manually_static_ref_access() {
126 let ms = ManuallyStatic::new(42);
127 let ms_ref = ms.get_ref();
128
129 assert_eq!(*ms_ref, 42);
130 }
131
132 #[test]
133 #[cfg(debug_assertions)] // This test only runs in debug mode
134 #[should_panic(
135 expected = "Attempted to dereference ManuallyStaticRef after ManuallyStatic was dropped!"
136 )]
137 fn test_manually_static_ref_panics_on_drop() {
138 let ms_ref;
139
140 {
141 let ms = ManuallyStatic::new(42);
142
143 ms_ref = ms.get_ref();
144 }
145
146 let _ = *ms_ref;
147 }
148
149 #[test]
150 #[cfg(not(debug_assertions))] // This test only runs in release mode
151 fn test_manually_static_ref_no_panic_on_drop_release_mode() {
152 let ms_ptr;
153
154 {
155 let ms = ManuallyStatic::new(42);
156
157 ms_ptr = ms.get_ref();
158 }
159
160 let _ = *ms_ptr;
161 }
162}