【问题标题】:How to pass closure argument to a method of a trait with a lifetime parameter?如何将闭包参数传​​递给具有生命周期参数的特征方法?
【发布时间】:2018-07-06 18:10:42
【问题描述】:

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

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

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

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

不幸的是,我不知道我应该如何为INeedSForSomething 提供与闭包参数s 相关的生命周期,这在do_scoped_stuff_in_a 方法之外是未知的。

我想我可以确保通过编写这样的生命周期:<'a: 'scope, 'b: 'scope, 'scope> 'scope 生命周期适合闭包参数,但看起来我在这里遗漏了一些东西......

这是我写的完整代码:

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的人提供见解...

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

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

【问题讨论】:

    标签: rust lifetime


    【解决方案1】:

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

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

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

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

    我们不能保证 s 的生命周期会被 f 闭包捕获的任何对象的一些生命周期参数所延长,因为无法从 fFnOnce(&amp;Scope) 签名中检索此生命周期参数.

    我可以说,在这种特殊情况下,调用闭包应该是安全的 - 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 相关的实际问题,它似乎也可以正常工作。

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

      • 不幸的是,这不是我的问题的解决方案。因为C 持有一个具有生命周期'c 的引用,所以它不能像那样传递,除非我可以通过指定生命周期约束('c: 'scope) 来保证'c 的寿命超过'scope。这些将只允许具有'c 生命周期的C 对象,其寿命超过'scope 来实现INeedSForSomething 特征。如果没有这些限制,编译器预计 C 持有的任何引用都是 'static
      • 问题是我似乎找不到任何方法告诉编译器 'scope 声明为 B::do_scoped_stuff_in_a 一部分的生命周期实际上是 &amp;Scope 的匿名生命周期传递给关闭。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-10
      • 2015-10-02
      • 2019-12-19
      • 2017-05-30
      • 2022-08-22
      • 1970-01-01
      相关资源
      最近更新 更多