Skip to content

Commit 5b8681d

Browse files
committed
Add Scope::add_destructor to attach custom destructors
1 parent 4e9a177 commit 5b8681d

File tree

3 files changed

+99
-2
lines changed

3 files changed

+99
-2
lines changed

src/scope.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ use crate::util::{
1919
/// See [`Lua::scope`] for more details.
2020
pub struct Scope<'scope, 'env: 'scope> {
2121
lua: LuaGuard,
22+
// Internal destructors run first, then user destructors (based on the declaration order)
2223
destructors: Destructors<'env>,
24+
user_destructors: UserDestructors<'env>,
2325
_scope_invariant: PhantomData<&'scope mut &'scope ()>,
2426
_env_invariant: PhantomData<&'env mut &'env ()>,
2527
}
@@ -29,11 +31,14 @@ type DestructorCallback<'a> = Box<dyn FnOnce(&RawLua, ValueRef) -> Vec<Box<dyn F
2931
// Implement Drop on Destructors instead of Scope to avoid compilation error
3032
struct Destructors<'a>(RefCell<Vec<(ValueRef, DestructorCallback<'a>)>>);
3133

34+
struct UserDestructors<'a>(RefCell<Vec<Box<dyn FnOnce() + 'a>>>);
35+
3236
impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
3337
pub(crate) fn new(lua: LuaGuard) -> Self {
3438
Scope {
3539
lua,
3640
destructors: Destructors(RefCell::new(Vec::new())),
41+
user_destructors: UserDestructors(RefCell::new(Vec::new())),
3742
_scope_invariant: PhantomData,
3843
_env_invariant: PhantomData,
3944
}
@@ -215,6 +220,31 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
215220
}
216221
}
217222

223+
/// Adds a destructor function to be run when the scope ends.
224+
///
225+
/// This functionality is useful for cleaning up any resources after the scope ends.
226+
///
227+
/// # Example
228+
///
229+
/// ```rust
230+
/// # use mlua::{Error, Lua, Result};
231+
/// # fn main() -> Result<()> {
232+
/// let lua = Lua::new();
233+
/// let ud = lua.create_any_userdata(String::from("hello"))?;
234+
/// lua.scope(|scope| {
235+
/// scope.add_destructor(|| {
236+
/// _ = ud.take::<String>();
237+
/// });
238+
/// // Run the code that uses `ud` here
239+
/// Ok(())
240+
/// })?;
241+
/// assert!(matches!(ud.borrow::<String>(), Err(Error::UserDataDestructed)));
242+
/// # Ok(())
243+
/// # }
244+
pub fn add_destructor(&'scope self, destructor: impl FnOnce() + 'env) {
245+
self.user_destructors.0.borrow_mut().push(Box::new(destructor));
246+
}
247+
218248
unsafe fn create_callback(&'scope self, f: ScopedCallback<'scope>) -> Result<Function> {
219249
let f = mem::transmute::<ScopedCallback, Callback>(f);
220250
let f = self.lua.create_callback(f)?;
@@ -271,3 +301,12 @@ impl Drop for Destructors<'_> {
271301
}
272302
}
273303
}
304+
305+
impl Drop for UserDestructors<'_> {
306+
fn drop(&mut self) {
307+
let destructors = mem::take(&mut *self.0.borrow_mut());
308+
for destructor in destructors {
309+
destructor();
310+
}
311+
}
312+
}

src/state.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1470,9 +1470,9 @@ impl Lua {
14701470
/// lifetimes only outlive the scope lifetime.
14711471
pub fn scope<'env, R>(
14721472
&self,
1473-
f: impl for<'scope> FnOnce(&'scope mut Scope<'scope, 'env>) -> Result<R>,
1473+
f: impl for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> Result<R>,
14741474
) -> Result<R> {
1475-
f(&mut Scope::new(self.lock_arc()))
1475+
f(&Scope::new(self.lock_arc()))
14761476
}
14771477

14781478
/// Attempts to coerce a Lua value into a String in a manner consistent with Lua's internal

tests/scope.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::cell::Cell;
22
use std::rc::Rc;
33
use std::string::String as StdString;
4+
use std::sync::Arc;
45

56
use mlua::{
67
AnyUserData, Error, Function, Lua, MetaMethod, ObjectLike, Result, String, UserData, UserDataFields,
@@ -66,6 +67,27 @@ fn test_scope_outer_lua_access() -> Result<()> {
6667
Ok(())
6768
}
6869

70+
#[test]
71+
fn test_scope_capture_scope() -> Result<()> {
72+
let lua = Lua::new();
73+
74+
let i = Cell::new(0);
75+
lua.scope(|scope| {
76+
let f = scope.create_function(|_, ()| {
77+
scope.create_function(|_, n: u32| {
78+
i.set(i.get() + n);
79+
Ok(())
80+
})
81+
})?;
82+
f.call::<Function>(())?.call::<()>(10)?;
83+
Ok(())
84+
})?;
85+
86+
assert_eq!(i.get(), 10);
87+
88+
Ok(())
89+
}
90+
6991
#[test]
7092
fn test_scope_userdata_fields() -> Result<()> {
7193
struct MyUserData<'a>(&'a Cell<i64>);
@@ -463,6 +485,42 @@ fn test_scope_any_userdata_ref_mut() -> Result<()> {
463485
Ok(())
464486
}
465487

488+
#[test]
489+
fn test_scope_destructors() -> Result<()> {
490+
let lua = Lua::new();
491+
492+
lua.register_userdata_type::<Arc<StdString>>(|reg| {
493+
reg.add_meta_method("__tostring", |_, data, ()| Ok(data.to_string()));
494+
})?;
495+
496+
let arc_str = Arc::new(StdString::from("foo"));
497+
498+
let ud = lua.create_any_userdata(arc_str.clone())?;
499+
lua.scope(|scope| {
500+
scope.add_destructor(|| {
501+
assert!(ud.take::<Arc<StdString>>().is_ok());
502+
});
503+
Ok(())
504+
})?;
505+
assert_eq!(Arc::strong_count(&arc_str), 1);
506+
507+
// Try destructing the userdata while it's borrowed
508+
let ud = lua.create_any_userdata(arc_str.clone())?;
509+
ud.borrow_scoped::<Arc<StdString>, _>(|arc_str| {
510+
assert_eq!(arc_str.as_str(), "foo");
511+
lua.scope(|scope| {
512+
scope.add_destructor(|| {
513+
assert!(ud.take::<Arc<StdString>>().is_err());
514+
});
515+
Ok(())
516+
})
517+
.unwrap();
518+
assert_eq!(arc_str.as_str(), "foo");
519+
})?;
520+
521+
Ok(())
522+
}
523+
466524
fn modify_userdata(lua: &Lua, ud: &AnyUserData) -> Result<()> {
467525
lua.load(
468526
r#"

0 commit comments

Comments
 (0)