It seems like several people have struggled with putting Rust values into Lua as UserData and then later extracting them from Lua again, hoping for everything to be nice and safe when the value comes back out.
Wrapping a value in something like Rc<RefCell<Option<_>>>
works, but is slightly cumbersome to work with. It also requires you to use an owned value. If you only have a mutable reference on hand, it seems like you cannot use it as a UserData. Scopes can create functions which are not 'static
so you can use borrowed values there, but this is also somewhat cumbersome.
I have tried to come up with a workaround for this, allowing Scope to create UserData from mut-borrowed values. Hoping to get opinions on whether this is implementation will work. Apologies if I have missed something obvious or something that has already been discussed. I have looked at #20, especially #20 (comment) .
The idea is to give a &'scope mut T
reference to the Scope
and store it as a raw pointer *mut T
, which is plain old data so it can have 'static
lifetime. Because we are in a Scope
, we know that the pointer will be deleted when the Scope
is destructed. The UserData
trait can be implemented for BorrowedUserData<T>
by running T::add_methods
with a modified UserDataMethods
implementation which extracts BorrowedUserData<T>
and dereferences the pointer whenever the T
is needed. Checking for multiple borrows is handled with in the same way as owned values, with RefCell.
Code diff here: luteberget@8d32cca
I am certainly no expert in unsafe Rust, so if I've misunderstood any of the following then this might not work:
- Giving out a
&'scope mut T
reference ensures that the corresponding *mut T
points to the T
for the duration of the borrow.
- The pointer is only accessed and dereferenced when borrowed through the RefCell, so the dereferenced pointer can be used normally as a
&T
or &mut T
in Rust code for the lifetime of the Ref
/RefMut
.
- I'm not sure a raw pointer is actually needed, maybe it would work with
mem::transmute
into static lifetime instead, or something else.
Also, the BorrowedUserData<T>
struct is internal to the crate, so users of the library cannot use AnyUserData::borrow
or similar. I think this could be fixed by checking for both T
and BorrowedUserData<T>
when borrowing from AnyUserData
.
Using this feature looks like this:
extern crate rlua;
use rlua::prelude::*;
#[derive(Debug)]
struct MyData {
val: u64,
}
impl LuaUserData for MyData {
fn add_methods(methods: &mut LuaUserDataMethods<Self>) {
methods.add_method_mut("increment", |_,this,()| {
this.val += 1;
Ok(())
});
}
}
fn main() -> LuaResult<()> {
let lua = Lua::new();
let mut x = MyData { val: 0 };
lua.scope(|s| {
lua.globals().set("x", s.borrow_userdata(&mut x)?)?;
// mutate x from Lua
lua.eval::<()>("x:increment()", None)?;
Ok(())
})?;
x.val += 1;
// x.val is now 2
Ok(())
}