mlua/scope.rs
1use std::cell::RefCell;
2use std::marker::PhantomData;
3use std::mem;
4
5use crate::error::{Error, Result};
6use crate::function::Function;
7use crate::state::{Lua, LuaGuard, RawLua};
8use crate::traits::{FromLuaMulti, IntoLuaMulti};
9use crate::types::{Callback, CallbackUpvalue, ScopedCallback, ValueRef};
10use crate::userdata::{AnyUserData, UserData, UserDataRegistry, UserDataStorage};
11use crate::util::{self, check_stack, get_metatable_ptr, get_userdata, take_userdata, StackGuard};
12
13/// Constructed by the [`Lua::scope`] method, allows temporarily creating Lua userdata and
14/// callbacks that are not required to be `Send` or `'static`.
15///
16/// See [`Lua::scope`] for more details.
17pub struct Scope<'scope, 'env: 'scope> {
18 lua: LuaGuard,
19 // Internal destructors run first, then user destructors (based on the declaration order)
20 destructors: Destructors<'env>,
21 user_destructors: UserDestructors<'env>,
22 _scope_invariant: PhantomData<&'scope mut &'scope ()>,
23 _env_invariant: PhantomData<&'env mut &'env ()>,
24}
25
26type DestructorCallback<'a> = Box<dyn FnOnce(&RawLua, ValueRef) -> Vec<Box<dyn FnOnce() + 'a>>>;
27
28// Implement Drop on Destructors instead of Scope to avoid compilation error
29struct Destructors<'a>(RefCell<Vec<(ValueRef, DestructorCallback<'a>)>>);
30
31struct UserDestructors<'a>(RefCell<Vec<Box<dyn FnOnce() + 'a>>>);
32
33impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
34 pub(crate) fn new(lua: LuaGuard) -> Self {
35 Scope {
36 lua,
37 destructors: Destructors(RefCell::new(Vec::new())),
38 user_destructors: UserDestructors(RefCell::new(Vec::new())),
39 _scope_invariant: PhantomData,
40 _env_invariant: PhantomData,
41 }
42 }
43
44 /// Wraps a Rust function or closure, creating a callable Lua function handle to it.
45 ///
46 /// This is a version of [`Lua::create_function`] that creates a callback which expires on
47 /// scope drop. See [`Lua::scope`] for more details.
48 pub fn create_function<F, A, R>(&'scope self, func: F) -> Result<Function>
49 where
50 F: Fn(&Lua, A) -> Result<R> + 'scope,
51 A: FromLuaMulti,
52 R: IntoLuaMulti,
53 {
54 unsafe {
55 self.create_callback(Box::new(move |rawlua, nargs| {
56 let args = A::from_stack_args(nargs, 1, None, rawlua)?;
57 func(rawlua.lua(), args)?.push_into_stack_multi(rawlua)
58 }))
59 }
60 }
61
62 /// Wraps a Rust mutable closure, creating a callable Lua function handle to it.
63 ///
64 /// This is a version of [`Lua::create_function_mut`] that creates a callback which expires
65 /// on scope drop. See [`Lua::scope`] and [`Scope::create_function`] for more details.
66 pub fn create_function_mut<F, A, R>(&'scope self, func: F) -> Result<Function>
67 where
68 F: FnMut(&Lua, A) -> Result<R> + 'scope,
69 A: FromLuaMulti,
70 R: IntoLuaMulti,
71 {
72 let func = RefCell::new(func);
73 self.create_function(move |lua, args| {
74 (*func.try_borrow_mut().map_err(|_| Error::RecursiveMutCallback)?)(lua, args)
75 })
76 }
77
78 /// Creates a Lua userdata object from a reference to custom userdata type.
79 ///
80 /// This is a version of [`Lua::create_userdata`] that creates a userdata which expires on
81 /// scope drop, and does not require that the userdata type be Send. This method takes
82 /// non-'static reference to the data. See [`Lua::scope`] for more details.
83 ///
84 /// Userdata created with this method will not be able to be mutated from Lua.
85 pub fn create_userdata_ref<T>(&'scope self, data: &'env T) -> Result<AnyUserData>
86 where
87 T: UserData + 'static,
88 {
89 let ud = unsafe { self.lua.make_userdata(UserDataStorage::new_ref(data)) }?;
90 self.seal_userdata::<T>(&ud);
91 Ok(ud)
92 }
93
94 /// Creates a Lua userdata object from a mutable reference to custom userdata type.
95 ///
96 /// This is a version of [`Lua::create_userdata`] that creates a userdata which expires on
97 /// scope drop, and does not require that the userdata type be Send. This method takes
98 /// non-'static mutable reference to the data. See [`Lua::scope`] for more details.
99 pub fn create_userdata_ref_mut<T>(&'scope self, data: &'env mut T) -> Result<AnyUserData>
100 where
101 T: UserData + 'static,
102 {
103 let ud = unsafe { self.lua.make_userdata(UserDataStorage::new_ref_mut(data)) }?;
104 self.seal_userdata::<T>(&ud);
105 Ok(ud)
106 }
107
108 /// Creates a Lua userdata object from a reference to custom Rust type.
109 ///
110 /// This is a version of [`Lua::create_any_userdata`] that creates a userdata which expires on
111 /// scope drop, and does not require that the Rust type be Send. This method takes non-'static
112 /// reference to the data. See [`Lua::scope`] for more details.
113 ///
114 /// Userdata created with this method will not be able to be mutated from Lua.
115 pub fn create_any_userdata_ref<T>(&'scope self, data: &'env T) -> Result<AnyUserData>
116 where
117 T: 'static,
118 {
119 let ud = unsafe { self.lua.make_any_userdata(UserDataStorage::new_ref(data)) }?;
120 self.seal_userdata::<T>(&ud);
121 Ok(ud)
122 }
123
124 /// Creates a Lua userdata object from a mutable reference to custom Rust type.
125 ///
126 /// This is a version of [`Lua::create_any_userdata`] that creates a userdata which expires on
127 /// scope drop, and does not require that the Rust type be Send. This method takes non-'static
128 /// mutable reference to the data. See [`Lua::scope`] for more details.
129 pub fn create_any_userdata_ref_mut<T>(&'scope self, data: &'env mut T) -> Result<AnyUserData>
130 where
131 T: 'static,
132 {
133 let ud = unsafe { self.lua.make_any_userdata(UserDataStorage::new_ref_mut(data)) }?;
134 self.seal_userdata::<T>(&ud);
135 Ok(ud)
136 }
137
138 /// Creates a Lua userdata object from a custom userdata type.
139 ///
140 /// This is a version of [`Lua::create_userdata`] that creates a userdata which expires on
141 /// scope drop, and does not require that the userdata type be `Send` or `'static`. See
142 /// [`Lua::scope`] for more details.
143 ///
144 /// The main limitation that comes from using non-'static userdata is that the produced userdata
145 /// will no longer have a [`TypeId`] associated with it, because [`TypeId`] can only work for
146 /// `'static` types. This means that it is impossible, once the userdata is created, to get a
147 /// reference to it back *out* of an [`AnyUserData`] handle. This also implies that the
148 /// "function" type methods that can be added via [`UserDataMethods`] (the ones that accept
149 /// [`AnyUserData`] as a first parameter) are vastly less useful. Also, there is no way to
150 /// re-use a single metatable for multiple non-'static types, so there is a higher cost
151 /// associated with creating the userdata metatable each time a new userdata is created.
152 ///
153 /// [`TypeId`]: std::any::TypeId
154 /// [`UserDataMethods`]: crate::UserDataMethods
155 pub fn create_userdata<T>(&'scope self, data: T) -> Result<AnyUserData>
156 where
157 T: UserData + 'env,
158 {
159 let state = self.lua.state();
160 unsafe {
161 let _sg = StackGuard::new(state);
162 check_stack(state, 3)?;
163
164 // We don't write the data to the userdata until pushing the metatable
165 let protect = !self.lua.unlikely_memory_error();
166 #[cfg(feature = "luau")]
167 let ud_ptr = {
168 let data = UserDataStorage::new_scoped(data);
169 util::push_userdata(state, data, protect)?
170 };
171 #[cfg(not(feature = "luau"))]
172 let ud_ptr = util::push_uninit_userdata::<UserDataStorage<T>>(state, protect)?;
173
174 // Push the metatable and register it with no TypeId
175 let mut registry = UserDataRegistry::new_unique(self.lua.lua(), ud_ptr as *mut _);
176 T::register(&mut registry);
177 self.lua.push_userdata_metatable(registry.into_raw())?;
178 let mt_ptr = ffi::lua_topointer(state, -1);
179 self.lua.register_userdata_metatable(mt_ptr, None);
180
181 // Write data to the pointer and attach metatable
182 #[cfg(not(feature = "luau"))]
183 std::ptr::write(ud_ptr, UserDataStorage::new_scoped(data));
184 ffi::lua_setmetatable(state, -2);
185
186 let ud = AnyUserData(self.lua.pop_ref());
187 self.seal_userdata::<T>(&ud);
188
189 Ok(ud)
190 }
191 }
192
193 /// Creates a Lua userdata object from a custom Rust type.
194 ///
195 /// Since the Rust type is not required to be static and implement [`UserData`] trait,
196 /// you need to provide a function to register fields or methods for the object.
197 ///
198 /// See also [`Scope::create_userdata`] for more details about non-static limitations.
199 pub fn create_any_userdata<T>(
200 &'scope self,
201 data: T,
202 register: impl FnOnce(&mut UserDataRegistry<T>),
203 ) -> Result<AnyUserData>
204 where
205 T: 'env,
206 {
207 let state = self.lua.state();
208 let ud = unsafe {
209 let _sg = StackGuard::new(state);
210 check_stack(state, 3)?;
211
212 // We don't write the data to the userdata until pushing the metatable
213 let protect = !self.lua.unlikely_memory_error();
214 #[cfg(feature = "luau")]
215 let ud_ptr = {
216 let data = UserDataStorage::new_scoped(data);
217 util::push_userdata(state, data, protect)?
218 };
219 #[cfg(not(feature = "luau"))]
220 let ud_ptr = util::push_uninit_userdata::<UserDataStorage<T>>(state, protect)?;
221
222 // Push the metatable and register it with no TypeId
223 let mut registry = UserDataRegistry::new_unique(self.lua.lua(), ud_ptr as *mut _);
224 register(&mut registry);
225 self.lua.push_userdata_metatable(registry.into_raw())?;
226 let mt_ptr = ffi::lua_topointer(state, -1);
227 self.lua.register_userdata_metatable(mt_ptr, None);
228
229 // Write data to the pointer and attach metatable
230 #[cfg(not(feature = "luau"))]
231 std::ptr::write(ud_ptr, UserDataStorage::new_scoped(data));
232 ffi::lua_setmetatable(state, -2);
233
234 AnyUserData(self.lua.pop_ref())
235 };
236 self.seal_userdata::<T>(&ud);
237 Ok(ud)
238 }
239
240 /// Adds a destructor function to be run when the scope ends.
241 ///
242 /// This functionality is useful for cleaning up any resources after the scope ends.
243 ///
244 /// # Example
245 ///
246 /// ```rust
247 /// # use mlua::{Error, Lua, Result};
248 /// # fn main() -> Result<()> {
249 /// let lua = Lua::new();
250 /// let ud = lua.create_any_userdata(String::from("hello"))?;
251 /// lua.scope(|scope| {
252 /// scope.add_destructor(|| {
253 /// _ = ud.take::<String>();
254 /// });
255 /// // Run the code that uses `ud` here
256 /// Ok(())
257 /// })?;
258 /// assert!(matches!(ud.borrow::<String>(), Err(Error::UserDataDestructed)));
259 /// # Ok(())
260 /// # }
261 pub fn add_destructor(&'scope self, destructor: impl FnOnce() + 'env) {
262 self.user_destructors.0.borrow_mut().push(Box::new(destructor));
263 }
264
265 unsafe fn create_callback(&'scope self, f: ScopedCallback<'scope>) -> Result<Function> {
266 let f = mem::transmute::<ScopedCallback, Callback>(f);
267 let f = self.lua.create_callback(f)?;
268
269 let destructor: DestructorCallback = Box::new(|rawlua, vref| {
270 let ref_thread = rawlua.ref_thread();
271 ffi::lua_getupvalue(ref_thread, vref.index, 1);
272 let upvalue = get_userdata::<CallbackUpvalue>(ref_thread, -1);
273 let data = (*upvalue).data.take();
274 ffi::lua_pop(ref_thread, 1);
275 vec![Box::new(move || drop(data))]
276 });
277 self.destructors.0.borrow_mut().push((f.0.clone(), destructor));
278
279 Ok(f)
280 }
281
282 /// Shortens the lifetime of the userdata to the lifetime of the scope.
283 fn seal_userdata<T: 'env>(&self, ud: &AnyUserData) {
284 let destructor: DestructorCallback = Box::new(|rawlua, vref| unsafe {
285 // Ensure that userdata is not destructed
286 match rawlua.get_userdata_ref_type_id(&vref) {
287 Ok(Some(_)) => {}
288 Ok(None) => {
289 // Deregister metatable
290 let mt_ptr = get_metatable_ptr(rawlua.ref_thread(), vref.index);
291 rawlua.deregister_userdata_metatable(mt_ptr);
292 }
293 Err(_) => return vec![],
294 }
295
296 let data = take_userdata::<UserDataStorage<T>>(rawlua.ref_thread(), vref.index);
297 vec![Box::new(move || drop(data))]
298 });
299 self.destructors.0.borrow_mut().push((ud.0.clone(), destructor));
300 }
301}
302
303impl Drop for Destructors<'_> {
304 fn drop(&mut self) {
305 // We separate the action of invalidating the userdata in Lua and actually dropping the
306 // userdata type into two phases. This is so that, in the event a userdata drop panics,
307 // we can be sure that all of the userdata in Lua is actually invalidated.
308
309 let destructors = mem::take(&mut *self.0.borrow_mut());
310 if let Some(lua) = destructors.first().map(|(vref, _)| vref.lua.lock()) {
311 // All destructors are non-panicking, so this is fine
312 let to_drop = destructors
313 .into_iter()
314 .flat_map(|(vref, destructor)| destructor(&lua, vref))
315 .collect::<Vec<_>>();
316
317 drop(to_drop);
318 }
319 }
320}
321
322impl Drop for UserDestructors<'_> {
323 fn drop(&mut self) {
324 let destructors = mem::take(&mut *self.0.borrow_mut());
325 for destructor in destructors {
326 destructor();
327 }
328 }
329}