manually_static/
lib.rs

1#![deny(clippy::all)]
2#![deny(clippy::assertions_on_result_states)]
3#![deny(clippy::match_wild_err_arm)]
4#![deny(clippy::allow_attributes_without_reason)]
5#![warn(clippy::pedantic)]
6#![warn(clippy::nursery)]
7#![warn(clippy::cargo)]
8#![allow(
9    clippy::missing_const_for_fn,
10    reason = "Since we cannot make a constant function non-constant after its release,
11    we need to look for a reason to make it constant, and not vice versa."
12)]
13#![allow(
14    clippy::must_use_candidate,
15    reason = "It is better to developer think about it."
16)]
17#![allow(
18    clippy::missing_errors_doc,
19    reason = "Unless the error is something special,
20    the developer should document it."
21)]
22
23use std::ops::{Deref, DerefMut};
24#[cfg(debug_assertions)]
25use std::sync::atomic::{AtomicBool, Ordering};
26#[cfg(debug_assertions)]
27use std::sync::Arc;
28
29/// `ManuallyStatic<T>` holds a value `T` and provides references to it
30/// with checking with `debug_assertions`.
31///
32/// In debug builds, it tracks if the original `ManuallyStatic` instance
33/// has been dropped, causing any derived `ManuallyStaticRef` or
34/// `ManuallyStaticRefMut` to panic upon dereference if the original
35/// owner is no longer alive.
36///
37/// This is useful for simulating a 'static lifetime where you want
38/// runtime checks for use-after-free in debug environments.
39pub struct ManuallyStatic<T> {
40    value: T,
41    /// This flag is only present in debug builds (`cfg(debug_assertions)`).
42    /// It is set to `true` when the `ManuallyStatic` instance is dropped.
43    #[cfg(debug_assertions)]
44    was_dropped: Arc<AtomicBool>,
45}
46
47impl<T> ManuallyStatic<T> {
48    /// Creates a new `ManuallyStatic` instance holding the given value.
49    pub fn new(value: T) -> Self {
50        Self {
51            value,
52            #[cfg(debug_assertions)]
53            was_dropped: Arc::new(AtomicBool::new(false)),
54        }
55    }
56
57    /// Returns a [`ManuallyStaticRef`] that provides immutable access to the
58    /// contained value. In debug builds, dereferencing this will panic
59    /// if the original `ManuallyStatic` instance has been dropped.
60    pub fn get_ref(&self) -> ManuallyStaticRef<T> {
61        ManuallyStaticRef {
62            value_ref: &self.value,
63            #[cfg(debug_assertions)]
64            was_dropped: self.was_dropped.clone(),
65        }
66    }
67
68    /// Returns a [`ManuallyStaticRefMut`] that provides mutable access to the
69    /// contained value. In debug builds, dereferencing this will panic
70    /// if the original `ManuallyStatic` instance has been dropped.
71    pub fn get_mut(&mut self) -> ManuallyStaticRefMut<T> {
72        ManuallyStaticRefMut {
73            value_ref_mut: &mut self.value,
74            #[cfg(debug_assertions)]
75            was_dropped: self.was_dropped.clone(),
76        }
77    }
78}
79
80/// Implements the `Drop` trait for [`ManuallyStatic<T>`] only in debug builds.
81/// When [`ManuallyStatic`] is dropped, it sets the `was_dropped` flag to `true`.
82#[cfg(debug_assertions)]
83impl<T> Drop for ManuallyStatic<T> {
84    fn drop(&mut self) {
85        self.was_dropped.store(true, Ordering::Release);
86    }
87}
88
89/// A reference to the value held by [`ManuallyStatic<T>`].
90/// In debug builds, it will panic if dereferenced after the
91/// original [`ManuallyStatic`] has been dropped.
92pub struct ManuallyStaticRef<T> {
93    value_ref: *const T,
94    #[cfg(debug_assertions)]
95    was_dropped: Arc<AtomicBool>,
96}
97
98impl<T> Deref for ManuallyStaticRef<T> {
99    type Target = T;
100
101    fn deref(&self) -> &T {
102        #[cfg(debug_assertions)]
103        {
104            assert!(
105                !self.was_dropped.load(Ordering::Acquire),
106                "ManuallyStaticRef: Attempted to dereference value after ManuallyStatic was dropped!"
107            );
108        }
109
110        unsafe { &*self.value_ref }
111    }
112}
113
114/// A mutable reference to the value held by `ManuallyStatic<T>`.
115/// In debug builds, it will panic if dereferenced after the
116/// original `ManuallyStatic` has been dropped.
117pub struct ManuallyStaticRefMut<T> {
118    value_ref_mut: *mut T,
119    #[cfg(debug_assertions)]
120    was_dropped: Arc<AtomicBool>,
121}
122
123impl<T> Deref for ManuallyStaticRefMut<T> {
124    type Target = T;
125
126    fn deref(&self) -> &T {
127        #[cfg(debug_assertions)]
128        {
129            assert!(
130                !self.was_dropped.load(Ordering::Acquire),
131                "ManuallyStaticRefMut: Attempted to dereference value after ManuallyStatic was dropped!"
132            );
133        }
134
135        unsafe { &*self.value_ref_mut }
136    }
137}
138
139impl<T> DerefMut for ManuallyStaticRefMut<T> {
140    fn deref_mut(&mut self) -> &mut T {
141        #[cfg(debug_assertions)]
142        {
143            assert!(
144                !self.was_dropped.load(Ordering::Acquire),
145                "ManuallyStaticRefMut: Attempted to dereference value after ManuallyStatic was dropped!"
146            );
147        }
148
149        unsafe { &mut *self.value_ref_mut }
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn test_manually_static_ref_access() {
159        let ms = ManuallyStatic::new(42);
160        let ms_ref = ms.get_ref();
161
162        assert_eq!(*ms_ref, 42);
163    }
164
165    #[test]
166    fn test_manually_static_ref_mut_access() {
167        let mut ms = ManuallyStatic::new(42);
168        let mut ms_mut = ms.get_mut();
169
170        *ms_mut = 100;
171
172        assert_eq!(*ms_mut, 100);
173        assert_eq!(*ms.get_ref(), 100);
174    }
175
176    #[test]
177    #[cfg(debug_assertions)] // This test only runs in debug mode
178    #[should_panic(expected = "Attempted to dereference value after ManuallyStatic was dropped!")]
179    fn test_manually_static_ref_panics_on_drop() {
180        let ms_ref;
181
182        {
183            let ms = ManuallyStatic::new(42);
184
185            ms_ref = ms.get_ref();
186        }
187
188        let _ = *ms_ref;
189    }
190
191    #[test]
192    #[cfg(debug_assertions)] // This test only runs in debug mode
193    #[should_panic(expected = "Attempted to dereference value after ManuallyStatic was dropped!")]
194    fn test_manually_static_ref_mut_panics_on_drop() {
195        let ms_mut;
196
197        {
198            let mut ms = ManuallyStatic::new(42);
199
200            ms_mut = ms.get_mut();
201        }
202
203        let _ = *ms_mut;
204    }
205
206    #[test]
207    #[cfg(not(debug_assertions))] // This test only runs in release mode
208    fn test_manually_static_ref_no_panic_on_drop_release_mode() {
209        let ms_ptr;
210
211        {
212            let ms = ManuallyStatic::new(42);
213
214            ms_ptr = ms.get_ref();
215        }
216
217        let _ = *ms_ptr;
218    }
219}