Skip to main content

rquickjs_core/
js_lifetime.rs

1use crate::{
2    atom, value::Constructor, Array, Atom, BigInt, CString, Exception, Function, Module, Object,
3    Promise, Proxy, String, Symbol, Value,
4};
5
6/// The trait which signifies a type using the rquickjs `'js` lifetime trick for maintaining safety around Javascript values.
7///
8/// # Safety
9///
10/// This trait can only be implemented for types which derive a `'js` lifetime from a Javascript
11/// value, directly or indirectly.
12///
13/// All of the base Javascript types used in rquickjs like [`Value`] have a `'js` lifetime. If a
14/// type wants to contains one of those types it must define a lifetime generic. This trait is for
15/// indicating that that lifetime is one derived from a Javascript value. Rquickjs needs to know
16/// about this lifetime so that it is able to ensure safe use of types.
17///
18/// This trait can be derived with `#[derive(JsLifetime)]` in most cases, however sometimes a manual
19/// implementation is required.
20///
21/// This trait must be implemented correctly, failing to do so will make it possible to create
22/// unsound behavior. Correct implementions have the `'js` lifetime in `JsLifetime<'js>` be the
23/// same as the lifetime on the container, furthermore the associated type `Changed<'to>` is
24/// defined as the exact same type with the only difference being that the `'js` lifetime is now
25/// `'to`.
26///
27/// The following is a correct implementation of the [`JsLifetime`] trait.
28/// ```
29/// # use rquickjs::JsLifetime;
30/// struct Container<'js>(rquickjs::Object<'js>);
31///
32/// unsafe impl<'js> JsLifetime<'js> for Container<'js>{
33///     type Changed<'to> = Container<'to>;
34/// }
35/// ```
36///
37/// If a type does not have any lifetimes associated with it or all the lifetimes are `'static`
38/// then if is always save to implement `JsLifetime`.
39///
40/// See correct example for a static type below.
41/// ```
42/// # use rquickjs::JsLifetime;
43/// struct Bytes(Vec<u8>);
44///
45/// unsafe impl<'js> JsLifetime<'js> for Bytes{
46///     type Changed<'to> = Bytes;
47/// }
48///
49/// ```
50///
51///
52/// ## Incorrect examples
53///
54/// For example the following is unsound!
55/// ```no_run
56/// # use rquickjs::JsLifetime;
57/// struct Container<'js>(rquickjs::Object<'js>);
58///
59/// unsafe impl<'a,'js> JsLifetime<'js> for Container<'a>{ // WRONG LIFETIME!
60///     type Changed<'to> = Container<'to>;
61/// }
62/// ```
63/// `Container` here is defined as having a `'a` lifetime where it should be `'js`.
64///
65/// The following is also incorrect
66///
67/// ```no_run
68/// # use rquickjs::JsLifetime;
69/// // Her 'a is not derived from an Javascript value type, but instead the lifetime of a reference.
70/// struct Container<'a,'js>(&'a rquickjs::Object<'js>);
71///
72/// // Non 'js lifetime marked as a 'js lifetime. Unsound!
73/// unsafe impl<'js> JsLifetime<'js> for Container<'js, 'js>{
74///     type Changed<'to> = Container<'to,'to>;
75/// }
76/// ```
77/// The lifetime marked must be from an rquickjs type with a defined `<'js>` lifetime, it cannot be a
78/// the lifetime of reference!
79///
80pub unsafe trait JsLifetime<'js> {
81    /// The target which has the same type as a `Self` but with another lifetime `'t`
82    type Changed<'to>: 'to;
83}
84
85macro_rules! outlive_impls {
86    ($($type:ident,)*) => {
87        $(
88            unsafe impl<'js> JsLifetime<'js> for $type<'js> {
89                type Changed<'to> = $type<'to>;
90                //type Static = $type<'static>;
91            }
92        )*
93    };
94}
95
96outlive_impls! {
97    Value,
98    Symbol,
99    String,
100    CString,
101    Object,
102    Array,
103    BigInt,
104    Function,
105    Constructor,
106    Promise,
107    Proxy,
108    Exception,
109    Atom,
110}
111
112macro_rules! impl_outlive{
113    ($($($ty:ident)::+$(<$($g:ident),+>)*),*$(,)?) => {
114        $(
115            unsafe impl<'js,$($($g,)*)*> JsLifetime<'js> for $($ty)::*$(<$($g,)*>)*
116            where
117                  $($($g: JsLifetime<'js>,)*)*
118            {
119                type Changed<'to> = $($ty)::*$(<$($g::Changed<'to>,)*>)*;
120                //type Static = $($ty)::*$(<$($g::Static,)*>)*;
121            }
122        )*
123    };
124}
125
126impl_outlive!(
127    u8,
128    u16,
129    u32,
130    u64,
131    usize,
132    u128,
133    i8,
134    i16,
135    i32,
136    i64,
137    isize,
138    i128,
139    char,
140    alloc::string::String,
141    alloc::vec::Vec<T>,
142    alloc::boxed::Box<T>,
143    core::option::Option<T>,
144    core::result::Result<T,E>,
145    core::cell::Cell<T>,
146    core::cell::RefCell<T>,
147    core::cell::UnsafeCell<T>,
148    alloc::collections::BTreeMap<K,V>,
149    alloc::collections::BTreeSet<K>,
150    alloc::collections::BinaryHeap<K>,
151    alloc::collections::LinkedList<T>,
152    alloc::collections::VecDeque<T>,
153    alloc::ffi::CString,
154    core::ops::Range<T>,
155    core::ops::RangeFrom<T>,
156    core::ops::RangeFull,
157    core::ops::RangeInclusive<T>,
158    core::ops::RangeTo<T>,
159    core::ops::RangeToInclusive<T>,
160    core::ops::Bound<T>,
161    core::ops::ControlFlow<B,C>,
162    alloc::rc::Rc<T>,
163    alloc::sync::Arc<T>,
164    atom::PredefinedAtom,
165);
166
167#[cfg(feature = "std")]
168impl_outlive!(
169    std::backtrace::Backtrace,
170    std::collections::HashMap<K,V>,
171    std::collections::HashSet<K>,
172    std::ffi::OsString,
173    std::process::Child,
174    std::process::Command,
175    std::process::ExitCode,
176    std::process::ExitStatus,
177    std::process::Output,
178    std::process::Stdio,
179    std::path::PathBuf,
180    std::sync::Mutex<T>,
181    std::sync::RwLock<T>,
182);
183
184unsafe impl<'js, T: JsLifetime<'js>> JsLifetime<'js> for Module<'js, T> {
185    type Changed<'to> = Module<'to, T::Changed<'to>>;
186}
187
188unsafe impl<'js> JsLifetime<'js> for () {
189    type Changed<'to> = ();
190}