0
votes

I am trying to use rlua to add Lua scripting to my application but I've run into a problem with closures and lifetimes.

I have a method scope that accepts a closure as its argument. The closure takes one argument s. I can capture other objects in the closure and I can even pass s to their methods, provided that I've set up the lifetimes properly.

I want to abstract these objects by using a trait INeedSForSomething that provides a method need_s that would accept s. This is where the problems start to rise.

scope is called inside of another method do_scoped_stuff_in_a. Ideally, I would like to pass a Box<INeedSForSomething> into this method and call need_s with s as its argument. The problem is that the INeedSForSomething trait has to specify s's lifetime in order to specify its relation to another lifetime of a reference contained inside the implementor. It's impossible to make this lifetime a generic parameter of need_s method; if it was possible the rest of the code would compile fine.

Unfortunately, I can't figure out how am I supposed to provide INeedSForSomething with a lifetime that is related to closure argument s which is unknown outside of the do_scoped_stuff_in_a method.

I thought I made sure that by writing lifetimes like that: <'a: 'scope, 'b: 'scope, 'scope> the 'scope lifetime would be suitable fo the closure argument, but it looks like I'm missing something here...

Here's the full code I wrote:

struct A {
    //...
}

struct Scope {
    //...
}

impl A {
    fn scope<F>(&self, f: F)
    where
        F: FnOnce(&Scope),
    {
        let scope = Scope {};
        f(&scope);
    }
}

struct B {
    a: A,
}

impl B {
    fn do_scoped_stuff_in_a<'a: 'scope, 'b: 'scope, 'scope>(
        &'a self,
        arg: &'b Box<INeedSForSomething<'scope>>,
    ) {
        self.a.scope(|s| {
            arg.need_s(s);
        });
    }
}

trait INeedSForSomething<'scope> {
    fn need_s(&self, scope: &'scope Scope);
}

struct C<'c> {
    i_have_a_reference_with_some_lifetime: &'c String,
}

impl<'c: 'scope, 'scope> INeedSForSomething<'scope> for C<'c> {
    fn need_s(&self, s: &'scope Scope) {
        //...
    }
}
error[E0312]: lifetime of reference outlives lifetime of borrowed content...
  --> src\main.rs:29:24
   |
29 |             arg.need_s(s);
   |                        ^
   |
note: ...the reference is valid for the lifetime 'scope as defined on the method body at 24:5...
  --> src\main.rs:24:5
   |
24 | /     fn do_scoped_stuff_in_a<'a: 'scope, 'b: 'scope, 'scope>(
25 | |         &'a self,
26 | |         arg: &'b Box<INeedSForSomething<'scope>>,
27 | |     ) {
...  |
30 | |         });
31 | |     }
   | |_____^
note: ...but the borrowed content is only valid for the anonymous lifetime #2 defined on the body at 28:22
  --> src\main.rs:28:22
   |
28 |           self.a.scope(|s| {
   |  ______________________^
29 | |             arg.need_s(s);
30 | |         });
   | |_________^

To give insight for those who are familiar with rlua...

I'm trying to create a system where I would have a trait that would allow me to register the implementor as a global table of callbacks to the methods of the implementor inside of a closure passed to rlua::scope. I'm doing this instead of creating UserData with methods, because I want to avoid the restriction that says UserData must be 'static. The method declared by the trait needs to take a reference to a rlua::Lua object as well as rlua::Scope passed to the closure. Unfortunately, if the implementor requires another lifetime (because it contains references to other objects for example) I need to make sure that the lifetime for rlua::Scope is within the bounds of these lifetimes. This leads me to a situation where I have to declare rlua::Scope's lifetime as a part of trait declaration.

Maybe this isn't a good approach to begin with, so let me know if you have any better ideas on how to create Lua objects that would allow me to mutate state of Rust's objects that are not 'static.

2

2 Answers

1
votes

I think what I was trying to achieve is just not possible in safe Rust.

Any argument passed to a closure is valid as long as it outlives it or is moved into it. The type signatures used for passing closures (FnOnce, FnMut and such...) omit any generic parameters (including lifetimes) of objects referenced by the closure. Given these two facts it's impossible for a compiler to compare lifetime of the argument (&Scope) passed into the closure with a lifetime parameter of object (C<'c>) referenced by the closure.

Here's an example of what I mean: let's modify A::scope to take a reference to an already created Scope object instead of creating one inside of th function's body:

impl A {
    fn scope<F>(&self, s: &Scope, f: F)
    where
        F: FnOnce(&Scope),
    {
        f(s);
    }
}

We can't guarantee that the lifetime of s will be outlived by some lifetime parameter of whatever object is captured by the f closure, because this lifetime parameter cannot be retreived from FnOnce(&Scope) signature of f.

I can tell that in this particular case calling the closure should be safe - s will always be outlived by any lifetime parameters of objects captured by the closure, because it's created and destroyed in the same call where the closure is executed giving it a narrower scope than any lifetime parameter an object referenced by arg can possibly have.

So I've got rid of all thse scope lifetimes as they are useless in this case and I tried using unsafe to transmute arg to be 'static just before referencing it inside the closure. The solution has worked for me pretty well.

struct A {
    //...
}

struct Scope {
    //...
}

impl A {
    fn scope<F>(&self, f: F)
    where
        F: FnOnce(& Scope),
    {
        let scope = Scope {};
        f(&scope);
    }
}

struct B {
    a: A,
}

impl B {
    fn do_scoped_stuff_in_a<'a>(
        &self,
        arg: &'a INeedSForSomething,
    ) {
        unsafe {
            let static_arg = std::mem::transmute::<&'a INeedSForSomething, &'static INeedSForSomething>(arg);
            self.a.scope(|s| {
                static_arg.need_s(s);
            });
        }
    }
}

trait INeedSForSomething {
    fn need_s(&self, scope: &Scope);
}

struct C<'c> {
    i_have_a_reference_with_some_lifetime: &'c String,
}

impl<'c> INeedSForSomething for C<'c> {
    fn need_s(&self, s: &Scope) {
        //...
    }
}

fn main() {
    let s = String::new();
    let c = C {
        i_have_a_reference_with_some_lifetime: &s
    };

    let b = B { a: A {} };
    b.do_scoped_stuff_in_a(&c);
}

I've already implemented the solution in my program to solve the actual problem related to rlua crate and it seems to work fine too.

0
votes

Perhaps you do not need a lifetime; maybe this code is ok for you?

impl B {
    fn do_scoped_stuff_in_a<'a: 'scope, 'b: 'scope, 'scope>(
        &'a self,
        arg: &'b Box<INeedSForSomething>,
    ) {
        self.a.scope(|s: &Scope| {
            arg.need_s(s);
        });
    }
}

trait INeedSForSomething {
    fn need_s(&self, scope: &Scope);
}

struct C<'c> {
    i_have_a_reference_with_some_lifetime: &'c String,
}

impl<'c> INeedSForSomething for C<'c> {
    fn need_s(&self, s: &Scope) {
        //...
    }
}