I'm wrapping a Rust object to be used from Lua. I need the object to be destroyed when neither Rust code nor Lua still has a reference to it, so the obvious (to me) solution is to use Rc<T>
, stored in Lua-managed memory.
The Lua API (I'm using rust-lua53 for now) lets you allocate a chunk of memory and attach methods and a finalizer to it, so I want to store an Rc<T>
into that chunk of memory.
My current attempt looks like. First, creating an object:
/* Allocate a block of uninitialized memory to use */
let p = state.new_userdata(mem::size_of::<Rc<T>>() as size_t) as *mut Rc<T>;
/* Make a ref-counted pointer to a Rust object */
let rc = Rc::<T>::new(...);
/* Store the Rc */
unsafe { ptr::write(p, rc) };
And in the finaliser:
let p: *mut Rc<T> = ...; /* Get a pointer to the item to finalize */
unsafe { ptr::drop_in_place(p) }; /* Release the object */
Now this seems to work (as briefly tested by adding a println!() to the drop
method). But is it correct and safe (as long as I make sure it's not accessed after finalization)? I don't feel confident enough in unsafe Rust to be sure that it's ok to ptr::write an Rc<T>
.
I'm also wondering about, rather than storing an Rc<T>
directly, storing an Option<Rc<T>>
; then instead of drop_in_place()
I would ptr::swap()
it with None
. This would make it easy to handle any use after finalization.
Now this seems to work (as briefly tested by adding a println!() to the drop method). But is it correct and safe (as long as I make sure it's not accessed after finalisation)? I don't feel confident enough in unsafe Rust to be sure that it's ok to ptr::write an
Rc<T>
.
Yes, you may ptr::write
any Rust type to any memory location. This "leaks" the Rc<T>
object, but writes a bit-equivalent to the target location.
When using it, you need to guarantee that no one modified it outside of Rust code and that you are still in the same thread as the one where it was created. If you want to be able to move across threads, you need to use Arc
.
Rust's thread safety cannot protect you here, because you are using raw pointers.
I'm also wondering about, rather than storing an
Rc<T>
directly, storing anOption<Rc<T>>
; then instead of drop_in_place() I would ptr::swap() it with None. This would make it easy to handle any use after finalisation.
The pendant to ptr::write
is ptr::read
. So if you can guarantee that no one ever tries to ptr::read
or drop_in_place()
the object, then you can just call ptr::read
(which returns the object) and use that object as you would use any other Rc<T>
object. You don't need to care about dropping or anything, because now it's back in Rust's control.
You should also be using new_userdata_typed
instead of new_userdata
, since that takes the memory handling off your hands. There are other convenience wrapper functions ending with the postfix _typed
for most userdata needs.
Your code will work; of course, note that the drop_in_place(p)
will just decrease the counter of the Rc
and only drop the contained T
if and only if it was the last reference, which is the correct action.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With