mlua icon indicating copy to clipboard operation
mlua copied to clipboard

Possibility for owned functions/values/etc to hold a `Weak<LuaInner>` instead of an `Arc<LuaInner>`?

Open jcm2606 opened this issue 1 year ago • 2 comments

I was wondering if there's any possibility for an alternative form of owned functions/values/etc that internally hold a Weak<LuaInner> instead of an Arc<LuaInner> to mitigate any possible reference cycles when passing said owned function/value/etc back into Lua. I'm currently working on a Rust project that uses Lua as a scripting language and intend on capturing a Lua function and storing it in a Rust struct as an owned function to act as a callback, but I would then like to pass that Rust struct back into Lua which, as outlined in Mlua's docs, can very easily cause a reference cycle. Currently I'm working around this by maintaining a hashmap that maps numerical IDs to owned functions which lets me instead pass the ID into Lua, and this does work but it comes with a significant caveat that I need to provide access to this hashmap wherever I may want to call the owned function. I am aware that using a Weak could be problematic in situations where you don't know for sure that Lua hasn't been dropped so I'm definitely not looking for the current implementation to switch to a Weak, rather I'd like this to be a possibly unsafe alternative for when you know for sure that Lua is still alive and hasn't been dropped yet.

jcm2606 avatar Feb 12 '24 14:02 jcm2606

I don't like strong pointer in owned types either. As you correctly noted, switching Arc to Weak would lead to unsoundness when Lua is dropped while we still have a valid reference attached to owned object.

Alternatively (what's on my mind now), owned types can keep a Weak reference but would require "upgrade" through an additional "proxy" type to temporary hold Arc. An example:

let lua = Lua::new();
let owned_table: OwnedTable = lua.globals().into_owned();
let table = owned_table.upgrade(); // a "proxy" object with strong reference to Lua
let print: Function = table.get("print")?;

It's not very ergonomic and has some limitations but should work.

Also, owned types can be implemented manually in user apps in a form like:

struct OwnedTable(Weak<Lua>, RegistryKey);

let lua = Arc::new(Lua::new());
let registry_key = lua.create_registry_value(lua.globals())?;
let owned_table = OwnedTable(Arc::downgrage(&lua), registry_key);

// to get access
let lua2 = owned_table.0.upgrade().unwrap();
let table: Table = lua2.registry_value(&owned_table.1)?;

it could be a good alternative to hashmaps (your current approach)

khvzak avatar Feb 12 '24 22:02 khvzak

Yep, this is actually close to what I had in mind for how owned objects holding Weaks would work. In my case I can guarantee that Lua isn't dropped while there's still weak references held in owned objects, as Lua lives throughout the entire duration of my program while all owned objects are created and dropped before the program ends, but for other cases where there isn't that guarantee this seems decent for making sure that you're aware of the risks.

jcm2606 avatar Feb 13 '24 04:02 jcm2606