piccolo_util/
freeze.rs

1use std::{cell::RefCell, marker::PhantomData, mem, rc::Rc};
2
3use thiserror::Error;
4
5#[derive(Debug, Copy, Clone, Eq, PartialEq, Error)]
6pub enum AccessError {
7    #[error("frozen value accessed outside of enclosing scope")]
8    Expired,
9    #[error("already borrowed incompatibly")]
10    BadBorrow,
11}
12
13/// Safely erase a lifetime from a value and temporarily store it in a shared handle.
14///
15/// Works by providing only limited access to the held value within an enclosing call to
16/// `FrozenScope::scope`. All cloned handles will refer to the same underlying value with its
17/// lifetime erased.
18///
19/// Useful for passing non-'static values into things that do not understand the Rust lifetime
20/// system and need unrestricted sharing, such as scripting languages.
21pub struct Frozen<F: for<'f> Freeze<'f>> {
22    inner: Rc<RefCell<Option<<F as Freeze<'static>>::Frozen>>>,
23}
24
25pub trait Freeze<'f>: 'static {
26    type Frozen: 'f;
27}
28
29pub struct DynFreeze<T: ?Sized>(PhantomData<T>);
30
31impl<'f, T: ?Sized + for<'a> Freeze<'a>> Freeze<'f> for DynFreeze<T> {
32    type Frozen = <T as Freeze<'f>>::Frozen;
33}
34
35#[macro_export]
36#[doc(hidden)]
37macro_rules! __scripting_Freeze {
38    ($f:lifetime => $frozen:ty) => {
39        $crate::freeze::DynFreeze::<
40            dyn for<$f> $crate::freeze::Freeze<$f, Frozen = $frozen>,
41        >
42    };
43    ($frozen:ty) => {
44        $crate::freeze::Freeze!['freeze => $frozen]
45    };
46}
47
48pub use crate::__scripting_Freeze as Freeze;
49
50impl<F: for<'a> Freeze<'a>> Clone for Frozen<F> {
51    fn clone(&self) -> Self {
52        Self {
53            inner: self.inner.clone(),
54        }
55    }
56}
57
58impl<F: for<'a> Freeze<'a>> Default for Frozen<F> {
59    fn default() -> Self {
60        Self {
61            inner: Rc::new(RefCell::new(None)),
62        }
63    }
64}
65
66impl<F: for<'a> Freeze<'a>> Frozen<F> {
67    /// Creates a new *invalid* `Frozen` handle.
68    pub fn new() -> Self {
69        Self::default()
70    }
71
72    pub fn in_scope<'f, R>(value: <F as Freeze<'f>>::Frozen, cb: impl FnOnce(Self) -> R) -> R {
73        let f = Self::new();
74        let p = f.clone();
75        FrozenScope::new().freeze(&f, value).scope(move || cb(p))
76    }
77
78    /// Returns true if this value is currently set by an enclosing `FrozenScope::scope`.
79    pub fn is_valid(&self) -> bool {
80        if let Ok(b) = self.inner.try_borrow() {
81            b.is_some()
82        } else {
83            true
84        }
85    }
86
87    pub fn try_with<R>(
88        &self,
89        f: impl for<'f> FnOnce(&<F as Freeze<'f>>::Frozen) -> R,
90    ) -> Result<R, AccessError> {
91        Ok(f(self
92            .inner
93            .try_borrow()
94            .map_err(|_| AccessError::BadBorrow)?
95            .as_ref()
96            .ok_or(AccessError::Expired)?))
97    }
98
99    /// # Panics
100    /// Panics if this handle is not currently valid or if the held value is already borrowed
101    /// mutably.
102    pub fn with<R>(&self, f: impl for<'f> FnOnce(&<F as Freeze<'f>>::Frozen) -> R) -> R {
103        self.try_with(f).unwrap()
104    }
105
106    pub fn try_with_mut<R>(
107        &self,
108        f: impl for<'f> FnOnce(&mut <F as Freeze<'f>>::Frozen) -> R,
109    ) -> Result<R, AccessError> {
110        Ok(f(self
111            .inner
112            .try_borrow_mut()
113            .map_err(|_| AccessError::BadBorrow)?
114            .as_mut()
115            .ok_or(AccessError::Expired)?))
116    }
117
118    /// # Panics
119    /// Panics if this handle is not currently valid or if the held value is already borrowed.
120    pub fn with_mut<R>(&self, f: impl for<'f> FnOnce(&mut <F as Freeze<'f>>::Frozen) -> R) -> R {
121        self.try_with_mut(f).unwrap()
122    }
123}
124
125/// Struct that enables setting the contents of multiple `Frozen<F>` handles for the body of a
126/// single callback.
127pub struct FrozenScope<D = ()>(D);
128
129impl Default for FrozenScope<()> {
130    fn default() -> Self {
131        FrozenScope(())
132    }
133}
134
135impl FrozenScope<()> {
136    pub fn new() -> Self {
137        Self(())
138    }
139}
140
141impl<D: DropGuard> FrozenScope<D> {
142    /// Sets the given frozen value for the duration of the `FrozenScope::scope` call.
143    pub fn freeze<'h, 'f, F: for<'a> Freeze<'a>>(
144        self,
145        handle: &'h Frozen<F>,
146        value: <F as Freeze<'f>>::Frozen,
147    ) -> FrozenScope<(FreezeGuard<'h, 'f, F>, D)> {
148        FrozenScope((
149            FreezeGuard {
150                value: Some(value),
151                handle,
152            },
153            self.0,
154        ))
155    }
156
157    /// Inside this call, all of the handles set with `FrozenScope::freeze` will be valid and can be
158    /// accessed with `Frozen::with` and `Frozen::with_mut`. The provided handles (and all clones of
159    /// them) are invalidated before this call to `FrozenScope::scope` returns.
160    ///
161    /// # Panics
162    /// Panics if any of the provided handles are already set inside another, outer
163    /// `FrozenScope::scope` call or if any handles were set with `FrozenScope::freeze` more than
164    /// once. The given handles must be used with only one `FrozenScope` at a time.
165    pub fn scope<R>(mut self, cb: impl FnOnce() -> R) -> R {
166        // SAFETY: Safety depends on a few things...
167        //
168        // 1) We turn non-'static values into a 'static ones, outside code should never be able to
169        //    observe the held 'static value, because it lies about the true lifetime.
170        //
171        // 2) The only way to interact with the held 'static value is through `Frozen::[try_]with`
172        //    and `Frozen::[try_]with_mut`, both of which require a callback that works with the
173        //    frozen type for *any* lifetime. This interaction is safe because the callbacks must
174        //    work for any lifetime, so they must work with the lifetime we have erased.
175        //
176        // 3) The 'static `Frozen<F>` handles must have their values unset before the body of
177        //    this function ends because we only know they live for at least the body of this
178        //    function, and we use drop guards for this.
179        unsafe {
180            self.0.set();
181        }
182        let r = cb();
183        drop(self.0);
184        r
185    }
186}
187
188pub trait DropGuard {
189    // Sets the held `Frozen` handle to the held value.
190    //
191    // SAFETY:
192    // This is unsafe because the `Frozen` handle can now be used to access the value independent of
193    // its lifetime and the borrow checker cannot check this.
194    //
195    // Implementers of this trait *must* unset the handle's held value when the value is dropped.
196    //
197    // Users of this trait *must* drop it before the lifetime of the held value ends.
198    unsafe fn set(&mut self);
199}
200
201impl DropGuard for () {
202    unsafe fn set(&mut self) {}
203}
204
205impl<A: DropGuard, B: DropGuard> DropGuard for (A, B) {
206    unsafe fn set(&mut self) {
207        self.0.set();
208        self.1.set();
209    }
210}
211
212pub struct FreezeGuard<'h, 'f, F: for<'a> Freeze<'a>> {
213    value: Option<<F as Freeze<'f>>::Frozen>,
214    handle: &'h Frozen<F>,
215}
216
217impl<'h, 'f, F: for<'a> Freeze<'a>> Drop for FreezeGuard<'h, 'f, F> {
218    fn drop(&mut self) {
219        if let Ok(mut v) = self.handle.inner.try_borrow_mut() {
220            *v = None;
221        } else {
222            // This should not be possible to trigger safely, because users cannot hold
223            // `Ref` or `RefMut` handles from the inner `RefCell` in the first place,
224            // and `Frozen` does not implement Send so we can't be in the body of
225            // `Frozen::with[_mut]` in another thread. However, if it somehow happens that
226            // we cannot drop the held value, this means that there is a live reference to
227            // it somewhere so we are forced to abort the process.
228            eprintln!("impossible! freeze lock held during drop guard, aborting!");
229            std::process::abort()
230        }
231    }
232}
233
234impl<'h, 'f, F: for<'a> Freeze<'a>> DropGuard for FreezeGuard<'h, 'f, F> {
235    unsafe fn set(&mut self) {
236        assert!(
237            !self.handle.is_valid(),
238            "handle already used in another `FrozenScope::scope` call"
239        );
240        *self.handle.inner.borrow_mut() = Some(mem::transmute::<
241            <F as Freeze<'f>>::Frozen,
242            <F as Freeze<'static>>::Frozen,
243        >(self.value.take().unwrap()));
244    }
245}
246
247#[cfg(test)]
248mod tests {
249    use super::*;
250
251    #[test]
252    fn test_freeze_works() {
253        struct F<'a>(&'a i32);
254
255        let i = 4;
256        Frozen::<Freeze![F<'freeze>]>::in_scope(F(&i), |f| {
257            f.with(|f| {
258                assert_eq!(*f.0, 4);
259            });
260        });
261    }
262
263    #[test]
264    fn test_freeze_expires() {
265        struct F<'a>(&'a i32);
266
267        type FrozenF = Frozen<Freeze![F<'freeze>]>;
268
269        let mut outer: Option<FrozenF> = None;
270
271        let i = 4;
272        FrozenF::in_scope(F(&i), |f| {
273            outer = Some(f.clone());
274        });
275
276        assert_eq!(
277            outer.unwrap().try_with(|f| {
278                assert_eq!(*f.0, 4);
279            }),
280            Err(AccessError::Expired)
281        );
282    }
283}