scoped_reference/
lib.rs

1#![deny(warnings)]
2#![cfg_attr(not(feature = "std"), no_std)]
3
4//! This crate provides runtime-checked borrow lifetimes. It allows one to store references of the form `&'a T` as structs with lifetime `'static`.
5//! This is useful in situations where a reference with a shorter lifetime cannot be stored naturally.
6//!
7//! The following example demonstrates the use of scoped references. Scoped references come in both mutable and immutable variants.
8//! If the underlying reference is dropped while scoped borrows to it still exist, then the program panics. Note that a panic
9//! will always cause an immediate abort - unwinding is not allowed - because allowing unwinding could lead to
10//! dangling references and undefined behavior.
11//!
12//! ```no_run
13//! # use scoped_reference::*;
14//! struct StaticBorrow(ScopedBorrow<i32>);
15//!
16//! # fn test_borrow_mut() {
17//! let mut x = 10;
18//! let borrowed_x = &mut x;
19//! let mut scoped_ref = ScopedReference::new_mut(borrowed_x);
20//!
21//! let mut mut_ref_to_x = scoped_ref.borrow_mut();
22//! *mut_ref_to_x = 9;
23//!
24//! // Panic: mut_ref_to_x is still out!
25//! // drop(scoped_ref);
26//!
27//! drop(mut_ref_to_x);
28//!
29//! let static_borrow = StaticBorrow(scoped_ref.borrow());
30//! assert_eq!(*static_borrow.0, 9);
31//!
32//! // Panic: static_borrow is still out!
33//! // drop(scoped_ref);
34//!
35//! drop(static_borrow);
36//! drop(scoped_ref);
37//! # }
38//! ```
39
40#[cfg(not(feature = "std"))]
41extern crate alloc;
42#[cfg(feature = "std")]
43use std as alloc;
44
45use alloc::sync::*;
46use core::fmt;
47use core::ops::*;
48use core::sync::atomic::*;
49
50/// Allows for obtaining references with `'static` lifetime via runtime
51/// borrow checking.
52pub struct ScopedReference<'a, T: ?Sized> {
53    reference: Result<&'a T, &'a mut T>,
54    alive: Arc<AtomicUsize>,
55}
56
57impl<'a, T: ?Sized> ScopedReference<'a, T> {
58    /// Creates a new scoped reference for the specified borrow.
59    pub fn new(reference: &'a T) -> Self {
60        let alive = Arc::new(AtomicUsize::new(0));
61        let reference = Ok(reference);
62        Self { reference, alive }
63    }
64
65    /// Creates a new scoped reference for the specifed mutable borrow.
66    pub fn new_mut(reference: &'a mut T) -> Self {
67        let alive = Arc::new(AtomicUsize::new(0));
68        let reference = Err(reference);
69        Self { reference, alive }
70    }
71
72    /// Obtains a dynamically-checked borrow to the current reference.
73    pub fn borrow(&self) -> ScopedBorrow<T> {
74        match &self.reference {
75            Ok(r) => {
76                self.alive.fetch_add(1, Ordering::Release);
77                ScopedBorrow {
78                    pointer: *r as *const T,
79                    alive: self.alive.clone(),
80                }
81            }
82            Err(r) => {
83                if self.alive.load(Ordering::Acquire) == usize::MAX {
84                    panic_abort(
85                        "Cannot borrow a lifetime mutably while it is already borrowed immutably.",
86                    );
87                } else {
88                    self.alive.fetch_add(1, Ordering::Release);
89                    ScopedBorrow {
90                        pointer: *r as *const T,
91                        alive: self.alive.clone(),
92                    }
93                }
94            }
95        }
96    }
97
98    /// Obtains a mutable dynamically-checked borrow to the current reference.
99    pub fn borrow_mut(&mut self) -> ScopedBorrowMut<T> {
100        if self.alive.load(Ordering::Acquire) != 0 {
101            panic_abort("Scoped lifetime is already borrowed.")
102        } else {
103            self.alive.store(usize::MAX, Ordering::Release);
104            ScopedBorrowMut {
105                pointer: unsafe {
106                    self.reference
107                        .as_mut()
108                        .map_err(|x| *x as *mut T)
109                        .unwrap_err_unchecked()
110                },
111                alive: self.alive.clone(),
112            }
113        }
114    }
115}
116
117impl<'a, T: ?Sized> fmt::Debug for ScopedReference<'a, T> {
118    fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
119        Ok(())
120    }
121}
122
123impl<'a, T: ?Sized> fmt::Display for ScopedReference<'a, T> {
124    fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
125        Ok(())
126    }
127}
128
129impl<'a, T: ?Sized> Drop for ScopedReference<'a, T> {
130    fn drop(&mut self) {
131        if self.alive.load(Ordering::Acquire) != 0 {
132            panic_abort("Scoped lifetime was dropped while a borrow was out.")
133        }
134    }
135}
136
137/// Represents a borrow with a runtime-checked lifetime.
138pub struct ScopedBorrow<T: ?Sized> {
139    pointer: *const T,
140    alive: Arc<AtomicUsize>,
141}
142
143impl<T: ?Sized> Deref for ScopedBorrow<T> {
144    type Target = T;
145
146    fn deref(&self) -> &Self::Target {
147        unsafe { &*self.pointer }
148    }
149}
150
151impl<T: ?Sized> Drop for ScopedBorrow<T> {
152    fn drop(&mut self) {
153        self.alive.fetch_sub(1, Ordering::Release);
154    }
155}
156
157impl<T: ?Sized> Clone for ScopedBorrow<T> {
158    fn clone(&self) -> Self {
159        self.alive.fetch_add(1, Ordering::Release);
160        Self {
161            pointer: self.pointer,
162            alive: self.alive.clone(),
163        }
164    }
165}
166
167impl<T: fmt::Debug + ?Sized> fmt::Debug for ScopedBorrow<T> {
168    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169        fmt::Debug::fmt(&**self, f)
170    }
171}
172
173impl<T: fmt::Display + ?Sized> fmt::Display for ScopedBorrow<T> {
174    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175        fmt::Display::fmt(&**self, f)
176    }
177}
178
179unsafe impl<T: ?Sized + Send> Send for ScopedBorrow<T> {}
180unsafe impl<T: ?Sized + Sync> Sync for ScopedBorrow<T> {}
181
182/// Represents a mutable borrow with a runtime-checked lifetime.
183pub struct ScopedBorrowMut<T: ?Sized> {
184    pointer: *mut T,
185    alive: Arc<AtomicUsize>,
186}
187
188impl<T: ?Sized> Deref for ScopedBorrowMut<T> {
189    type Target = T;
190
191    fn deref(&self) -> &Self::Target {
192        unsafe { &*self.pointer }
193    }
194}
195
196impl<T: ?Sized> DerefMut for ScopedBorrowMut<T> {
197    fn deref_mut(&mut self) -> &mut Self::Target {
198        unsafe { &mut *self.pointer }
199    }
200}
201
202impl<T: ?Sized> Drop for ScopedBorrowMut<T> {
203    fn drop(&mut self) {
204        self.alive.store(0, Ordering::Release);
205    }
206}
207
208impl<T: fmt::Debug + ?Sized> fmt::Debug for ScopedBorrowMut<T> {
209    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210        fmt::Debug::fmt(&**self, f)
211    }
212}
213
214impl<T: fmt::Display + ?Sized> fmt::Display for ScopedBorrowMut<T> {
215    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216        fmt::Display::fmt(&**self, f)
217    }
218}
219
220unsafe impl<T: ?Sized + Send> Send for ScopedBorrowMut<T> {}
221unsafe impl<T: ?Sized + Sync> Sync for ScopedBorrowMut<T> {}
222
223#[allow(unreachable_code)]
224fn panic_abort(error: &str) -> ! {
225    #[cfg(panic = "abort")]
226    {
227        panic!("{error}");
228    }
229    #[cfg(all(not(panic = "abort"), feature = "std"))]
230    {
231        println!("{error}");
232        std::process::abort();
233    }
234    #[cfg(all(not(panic = "abort"), not(feature = "std")))]
235    {
236        struct Abort;
237
238        // Panic in a drop while panicking aborts the process
239        impl Drop for Abort {
240            fn drop(&mut self) {
241                panic!();
242            }
243        }
244
245        #[allow(unused_variables)]
246        let abort = Abort;
247
248        panic!("{error}");
249
250        core::mem::forget(abort);
251    }
252}
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257
258    struct StaticBorrow(ScopedBorrow<i32>);
259
260    #[test]
261    fn test_borrow_mut() {
262        let mut x = 10;
263        let borrowed_x = &mut x;
264        let mut scoped_ref = ScopedReference::new_mut(borrowed_x);
265
266        let mut mut_ref_to_x = scoped_ref.borrow_mut();
267        *mut_ref_to_x = 9;
268
269        // Panic: mut_ref_to_x is still out!
270        // drop(scoped_ref);
271
272        drop(mut_ref_to_x);
273
274        let static_borrow = StaticBorrow(scoped_ref.borrow());
275        assert_eq!(*static_borrow.0, 9);
276
277        // Panic: static_borrow is still out!
278        // drop(scoped_ref);
279
280        drop(static_borrow);
281        drop(scoped_ref);
282    }
283}