mlua-rs / rlua

High level Lua bindings to Rust
Other
1.72k stars 115 forks source link

Collect common problems into FAQ #85

Closed poga closed 2 years ago

poga commented 6 years ago

While using rlua, I encountered a lot of lifetime problem which are already described in previous issues (e.g. https://github.com/kyren/rlua/issues/20#issuecomment-405751583, https://github.com/kyren/rlua/issues/41, https://github.com/kyren/rlua/issues/74...). After reading those issues, using rlua becomes much easier.

It would be great if we can collect these design rationale into a FAQ document for beginners to lookup. I can help if you're ok about the idea.

Here's a list of issues/comment I think are really helpful:

Why 'static lifetime is required when passing data to Lua?

The reason I mention the GC is because that is the reason WHY userdata must be 'static: since we don't know when Lua will get around to garbage collecting our userdata, we can't make any assumptions about the lifetimes of any references in the userdata. Lua::scope forces the userdatas to be destructed at the exit of the scope.

How do I store Table, Function, or other rlua reference types outside of Lua?

There are two rules about rlua reference types that you're butting up against:

rlua reference types (Table, Function, etc) hold references to Lua, and are not really designed to be stored along-side Lua, because that would require self borrows. You CAN make structs in Rust that self borrow, but you really don't want to go down that road, it's very complex and you 99.999% of the time don't need it.

rlua reference types are also not designed to be stored inside userdata inside Lua. When I say userdata here, I mean both actual UserData types and also things like Rust callbacks. For one, there are some actual safety issues if you were able to do that, but more importantly Lua itself is not really designed for this either. Lua's C API does not provide a way of telling Lua that a userdata contains a registry reference, so you would end up potentially making something that cannot be garbage collected. The actual issue is very complicated but you can read about it some here.

kyren commented 6 years ago

Yeah, very very much agreed. I'm definitely okay with the idea if you want to take a stab at it, and I might have some more things in the FAQ after you're done as well.

Some of the discussion in the comments you linked may not apply in the near future though if we merge #86, but then that will just make NEW things that need to go in the FAQ.

zacps commented 5 years ago

I'm not sure if this counts as a common problem, but I couldn't figure out how to set the include path for require. Might just be my lack of knowledge of lua showing though.

poga commented 5 years ago

@zacps The search path used by Lua loaders is specified in package.path variable. See the manual for more detail. You might want to read the whole Module section if you want to customize the behavior of loaders.

But yeah, I don't think this question is specified to rlua. It's more like a general Lua issue.

zacps commented 5 years ago

I solved that, thanks for the tip. I think I've run into the first problem above.

My goal is to write a type Data which wraps a lua table and implements MetaMethod::Index so it can provide some methods/values. One of the methods needs to return a reference to the table that it wraps.

What I want to be able to write is something like:

struct DataTest {
    table: RegistryKey
}

impl UserData for DataTest {
    fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
        methods.add_meta_method(MetaMethod::Index, |lua, this, k: Value<'lua>| {
            Ok(lua.create_function::<Value<'lua>, _, _>(|lua, args| {
                Ok(Value::Table(lua.registry_value(&this.table)?))
            })?)
        });
    }
}

Which gives:

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
   --> src\main.rs:139:57
    |
139 |               Ok(lua.create_function::<Value<'lua>, _, _>(|lua, args| {
    |  _________________________________________________________^
140 | |                 Ok(Value::Table(lua.registry_value(&this.table)?))
141 | |             })?)
    | |_____________^
    |
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 138:52...
   --> src\main.rs:138:52
    |
138 |           methods.add_meta_method(MetaMethod::Index, |lua, this, k: Value<'lua>| {
    |  ____________________________________________________^
139 | |             Ok(lua.create_function::<Value<'lua>, _, _>(|lua, args| {
140 | |                 Ok(Value::Table(lua.registry_value(&this.table)?))
141 | |             })?)
142 | |         });
    | |_________^
    = note: ...so that the types are compatible:
            expected &&DataTest
               found &&DataTest
    = note: but, the lifetime must be valid for the static lifetime...
note: ...so that the type `[closure@src\main.rs:139:57: 141:14 this:&&DataTest]` will meet its required lifetime bounds
   --> src\main.rs:139:20
    |
139 |             Ok(lua.create_function::<Value<'lua>, _, _>(|lua, args| {
    |                    ^^^^^^^^^^^^^^^

I read your comment here, but unfortunately changing the Lua API is not possible in this case. Is there any other workaround?

jugglerchris commented 2 years ago

I have merged the FAQ.

About this particular lifetime issue, the problem is that you can't capture a reference to a non-'static Rust value (i.e. the DataTest reference which is only valid inside the meta method) in the closure passed to create_function(). One way to do it is to use add_meta_function and capture the user data (as a Lua value) in the inner Lua function using Function::bind():

impl UserData for DataTest {
    fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
        methods.add_meta_function(MetaMethod::Index, |lua, (this, _k): (Value<'lua>, Value<'lua>)| {
            let f = lua.create_function::<AnyUserData, _, _>(|lua, this| {
                let t = this.borrow::<DataTest>()?;
                Ok(Value::Table(lua.registry_value(&t.table)?))
            })?;
            Ok(f.bind(this)?)
        });
    }
}

I've checked that this compiles at least!