如何将闭包参数传​​递给具有生命周期参数的特征方法?

How to pass closure argument to a method of a trait with a lifetime parameter?

我正在尝试使用 rlua 将 Lua 脚本添加到我的应用程序,但我 运行 遇到了闭包和生命周期问题。

我有一个方法 scope 接受一个闭包作为它的参数。闭包接受一个参数 s。我可以捕获闭包中的其他对象,我什至可以将 s 传递给它们的方法,前提是我已经正确设置了生命周期。

我想通过使用特征 INeedSForSomething 来抽象这些对象,该特征 INeedSForSomething 提供了一个接受 s 的方法 need_s。这是问题开始出现的地方。

scope 在另一个方法 do_scoped_stuff_in_a 中被调用。理想情况下,我想将 Box<INeedSForSomething> 传递给此方法,并以 s 作为参数调用 need_s。问题是 INeedSForSomething 特性必须指定 s 的生命周期,以便指定它与包含在实现程序中的引用的另一个生命周期的关系。不可能使此生命周期成为 need_s 方法的通用参数;如果可能的话,其余的代码可以正常编译。

不幸的是,我无法弄清楚我应该如何为 INeedSForSomething 提供与闭包参数 s 相关的生命周期,而闭包参数 sdo_scoped_stuff_in_a 方法之外是未知的.

我想我确保通过这样写生命周期:<'a: 'scope, 'b: 'scope, 'scope> 'scope 生命周期将是 suitable 闭包参数,但看起来我遗漏了这里有东西...

这是我写的完整代码:

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 | |         });
   | |_________^

为熟悉 rlua 的人提供见解...

我正在尝试创建一个系统,在该系统中,我将拥有一个特征,允许我将实现者注册为全局 table 回调函数,回调函数在传递给 rlua::scope。我这样做而不是用方法创建 UserData,因为我想避免 UserData 必须是 'static 的限制。特征声明的方法需要引用 rlua::Lua 对象以及传递给闭包的 rlua::Scope 。不幸的是,如果实现者需要另一个生命周期(例如因为它包含对其他对象的引用),我需要确保 rlua::Scope 的生命周期在这些生命周期的范围内。这导致我不得不声明 rlua::Scope 的生命周期作为特征声明的一部分。

也许这不是一个好的开始方法,所以如果您对如何创建 Lua 对象有任何更好的想法,请告诉我,这些对象将允许我改变 Rust 对象的状态,而不是'static.

也许你不需要一辈子;也许这段代码适合你?

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) {
        //...
    }
}

我认为我试图实现的目标在安全 Rust 中是不可能的。

传递给闭包的任何参数都是有效的,只要它比它长或被移入它。用于传递闭包的类型签名(FnOnceFnMut 等...)省略了闭包引用的对象的任何通用参数(包括生命周期)。鉴于这两个事实,编译器不可能将传递给闭包的参数 (&Scope) 的生命周期与闭包引用的对象 (C<'c>) 的生命周期参数进行比较。

这是我的意思的一个例子:让我们修改 A::scope 以引用一个已经创建的 Scope 对象,而不是在函数主体内部创建一个对象:

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

我们不能保证 s 的生命周期会比 f 闭包捕获的任何对象的某些生命周期参数都长,因为这个生命周期参数不能从 FnOnce(&Scope) f.

的签名

我可以说在这种特殊情况下调用闭包应该是安全的 - s 将总是比闭包捕获的对象的任何生命周期参数都长寿,因为它是在同一个调用中创建和销毁的闭包的执行赋予它比 arg 引用的对象可能具有的任何生命周期参数更窄的范围。

所以我摆脱了所有这些 scope 生命周期,因为它们在这种情况下毫无用处,我尝试使用 unsafearg 转变为 'static就在闭包中引用它之前。该解决方案对我来说效果很好。

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);
}

我已经在我的程序中实现了解决与 rlua crate 相关的实际问题的解决方案,它似乎也能正常工作。