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 panics 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
117impl<T> Clone for ManuallyStaticRef<T> {
118 fn clone(&self) -> Self {
119 Self {
120 value_ref: self.value_ref,
121 #[cfg(debug_assertions)]
122 was_dropped: self.was_dropped.clone(),
123 }
124 }
125}
126
127unsafe impl<T: Send> Send for ManuallyStaticRef<T> {}
128unsafe impl<T: Sync> Sync for ManuallyStaticRef<T> {}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
135 fn test_manually_static_ref_access() {
136 let ms = ManuallyStatic::new(42);
137 let ms_ref = ms.get_ref();
138
139 assert_eq!(*ms_ref, 42);
140 }
141
142 #[test]
143 #[cfg(debug_assertions)] // This test only runs in debug mode
144 #[should_panic(
145 expected = "Attempted to dereference ManuallyStaticRef after ManuallyStatic was dropped!"
146 )]
147 fn test_manually_static_ref_panics_on_drop() {
148 let ms_ref;
149
150 {
151 let ms = ManuallyStatic::new(42);
152
153 ms_ref = ms.get_ref();
154 }
155
156 let _ = *ms_ref;
157 }
158
159 #[test]
160 #[cfg(not(debug_assertions))] // This test only runs in release mode
161 fn test_manually_static_ref_no_panic_on_drop_release_mode() {
162 let ms_ptr;
163
164 {
165 let ms = ManuallyStatic::new(42);
166
167 ms_ptr = ms.get_ref();
168 }
169
170 let _ = *ms_ptr;
171 }
172}