rquickjs_core/
persistent.rs

1use crate::{qjs, Ctx, Error, FromJs, IntoJs, JsLifetime, Result, Value};
2
3use std::{
4    fmt,
5    mem::{self, ManuallyDrop},
6};
7
8/// The wrapper for JS values to keep it from GC
9///
10/// For example you can store JS functions for later use.
11/// ```
12/// # use rquickjs::{Runtime, Context, Persistent, Function};
13/// # let rt = Runtime::new().unwrap();
14/// # let ctx = Context::full(&rt).unwrap();
15/// let func = ctx.with(|ctx| {
16///     Persistent::save(&ctx, ctx.eval::<Function, _>("a => a + 1").unwrap())
17/// });
18/// let res: i32 = ctx.with(|ctx| {
19///     let func = func.clone().restore(&ctx).unwrap();
20///     func.call((2,)).unwrap()
21/// });
22/// assert_eq!(res, 3);
23/// let res: i32 = ctx.with(|ctx| {
24///     let func = func.restore(&ctx).unwrap();
25///     func.call((0,)).unwrap()
26/// });
27/// assert_eq!(res, 1);
28/// ```
29///
30/// It is an error (`Error::UnrelatedRuntime`) to restore the `Persistent` in a
31/// context who isn't part of the original `Runtime`.
32///
33/// NOTE: Be careful and ensure that no persistent links outlives the runtime,
34/// otherwise Runtime will abort the process when dropped.
35///
36#[derive(Eq, PartialEq, Hash)]
37pub struct Persistent<T> {
38    pub(crate) rt: *mut qjs::JSRuntime,
39    pub(crate) value: T,
40}
41
42impl<T: Clone> Clone for Persistent<T> {
43    fn clone(&self) -> Self {
44        Persistent {
45            rt: self.rt,
46            value: self.value.clone(),
47        }
48    }
49}
50
51impl<T> fmt::Debug for Persistent<T>
52where
53    T: fmt::Debug,
54{
55    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
56        fmt.debug_struct("Persistent")
57            .field("rt", &self.rt)
58            .field("value", &self.value)
59            .finish()
60    }
61}
62
63impl<T> Persistent<T> {
64    unsafe fn outlive_transmute<'from, 'to, U>(t: U) -> U::Changed<'to>
65    where
66        U: JsLifetime<'from>,
67    {
68        // extremely unsafe code which should be safe if outlive is implemented correctly.
69
70        // assertion to check if T and T::Target are the same size, they should be.
71        // should compile away if they are the same size.
72        assert_eq!(mem::size_of::<U>(), mem::size_of::<U::Changed<'static>>());
73        assert_eq!(mem::align_of::<U>(), mem::align_of::<U::Changed<'static>>());
74
75        // union to transmute between two unrelated types
76        // Can't use transmute since it is unable to determine the size of both values.
77        union Transmute<A, B> {
78            a: ManuallyDrop<A>,
79            b: ManuallyDrop<B>,
80        }
81        let data = Transmute::<U, U::Changed<'to>> {
82            a: ManuallyDrop::new(t),
83        };
84        unsafe { ManuallyDrop::into_inner(data.b) }
85    }
86
87    /// Save the value of an arbitrary type
88    pub fn save<'js>(ctx: &Ctx<'js>, val: T) -> Persistent<T::Changed<'static>>
89    where
90        T: JsLifetime<'js>,
91    {
92        let outlived: T::Changed<'static> =
93            unsafe { Self::outlive_transmute::<'js, 'static, T>(val) };
94        let ptr = unsafe { qjs::JS_GetRuntime(ctx.as_ptr()) };
95        Persistent {
96            rt: ptr,
97            value: outlived,
98        }
99    }
100
101    /// Restore the value of an arbitrary type
102    pub fn restore<'js>(self, ctx: &Ctx<'js>) -> Result<T::Changed<'js>>
103    where
104        T: JsLifetime<'static>,
105    {
106        let ctx_runtime_ptr = unsafe { qjs::JS_GetRuntime(ctx.as_ptr()) };
107        if self.rt != ctx_runtime_ptr {
108            return Err(Error::UnrelatedRuntime);
109        }
110        Ok(unsafe { Self::outlive_transmute::<'static, 'js, T>(self.value) })
111    }
112}
113
114impl<'js, T, R> FromJs<'js> for Persistent<R>
115where
116    R: JsLifetime<'static, Changed<'js> = T>,
117    T: JsLifetime<'js, Changed<'static> = R> + FromJs<'js>,
118{
119    fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Persistent<R>> {
120        let value = T::from_js(ctx, value)?;
121        Ok(Persistent::save(ctx, value))
122    }
123}
124
125impl<'js, T> IntoJs<'js> for Persistent<T>
126where
127    T: JsLifetime<'static>,
128    T::Changed<'js>: IntoJs<'js>,
129{
130    fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
131        self.restore(ctx)?.into_js(ctx)
132    }
133}
134
135#[cfg(test)]
136mod test {
137    use crate::*;
138
139    #[test]
140    #[should_panic(expected = "UnrelatedRuntime")]
141    fn different_runtime() {
142        let rt1 = Runtime::new().unwrap();
143        let ctx = Context::full(&rt1).unwrap();
144
145        let persistent_v = ctx.with(|ctx| {
146            let v: Value = ctx.eval("1").unwrap();
147            Persistent::save(&ctx, v)
148        });
149
150        let rt2 = Runtime::new().unwrap();
151        let ctx = Context::full(&rt2).unwrap();
152        ctx.with(|ctx| {
153            let _ = persistent_v.clone().restore(&ctx).unwrap();
154        });
155    }
156
157    #[test]
158    fn different_context() {
159        let rt1 = Runtime::new().unwrap();
160        let ctx1 = Context::full(&rt1).unwrap();
161        let ctx2 = Context::full(&rt1).unwrap();
162
163        let persistent_v = ctx1.with(|ctx| {
164            let v: Object = ctx.eval("({ a: 1 })").unwrap();
165            Persistent::save(&ctx, v)
166        });
167
168        std::mem::drop(ctx1);
169
170        ctx2.with(|ctx| {
171            let obj: Object = persistent_v.clone().restore(&ctx).unwrap();
172            assert_eq!(obj.get::<_, i32>("a").unwrap(), 1);
173        });
174    }
175
176    #[test]
177    fn persistent_function() {
178        let rt = Runtime::new().unwrap();
179        let ctx = Context::full(&rt).unwrap();
180
181        let func = ctx.with(|ctx| {
182            let func: Function = ctx.eval("a => a + 1").unwrap();
183            Persistent::save(&ctx, func)
184        });
185
186        let res: i32 = ctx.with(|ctx| {
187            let func = func.clone().restore(&ctx).unwrap();
188            func.call((2,)).unwrap()
189        });
190        assert_eq!(res, 3);
191
192        let ctx2 = Context::full(&rt).unwrap();
193        let res: i32 = ctx2.with(|ctx| {
194            let func = func.restore(&ctx).unwrap();
195            func.call((0,)).unwrap()
196        });
197        assert_eq!(res, 1);
198    }
199
200    #[test]
201    fn persistent_value() {
202        let rt = Runtime::new().unwrap();
203        let ctx = Context::full(&rt).unwrap();
204
205        let persistent_v = ctx.with(|ctx| {
206            let v: Value = ctx.eval("1").unwrap();
207            Persistent::save(&ctx, v)
208        });
209
210        ctx.with(|ctx| {
211            let v = persistent_v.clone().restore(&ctx).unwrap();
212            ctx.globals().set("v", v).unwrap();
213            let eq: Value = ctx.eval("v == 1").unwrap();
214            assert!(eq.as_bool().unwrap());
215        });
216    }
217}