参数类型"T"的寿命可能不够长

b1z*_*zzu 2 generics closures rust

我正在尝试在Rust写一个小程序,但我无法让它工作.

我在一个较小的脚本中重现了错误:

fn main() {
    let name = String::from("World");
    let test = simple(name);
    println!("Hello {}!", test())
}

fn simple<T>(a: T) -> Box<Fn() -> T> {
    Box::new(move || -> T {
        a
    })
}
Run Code Online (Sandbox Code Playgroud)

当我编译它时,我收到此错误:

error[E0310]: the parameter type `T` may not live long enough
  --> test.rs:8:9
   |
7  |       fn simple<T>(a: T) -> Box<Fn() -> T> {
   |                 - help: consider adding an explicit lifetime bound `T: 'static`...
8  | /         Box::new(move || -> T {
9  | |             a
10 | |         })
   | |__________^
   |
note: ...so that the type `[closure@test.rs:8:18: 10:10 a:T]` will meet its required lifetime bounds
  --> test.rs:8:9
   |
8  | /         Box::new(move || -> T {
9  | |             a
10 | |         })
   | |__________^
Run Code Online (Sandbox Code Playgroud)

我试图添加一个明确的生命周期绑定,T: 'static如错误所示,但我收到一个新错误:

error[E0507]: cannot move out of captured outer variable in an `Fn` closure
 --> test.rs:9:13
  |
7 |     fn simple<T: 'static>(a: T) -> Box<Fn() -> T> {
  |                           - captured outer variable
8 |         Box::new(move || -> T {
9 |             a
  |             ^ cannot move out of captured outer variable in an `Fn` closure
Run Code Online (Sandbox Code Playgroud)

Dan*_*lme 7

这里有几件事情,这一切都与移动语义和闭包有轻微的尴尬有关.

首先,该simple函数需要为其T参数指定生命周期.从函数的角度来看,T可以是任何类型,这意味着它可以是一个引用,因此它需要有一个生命周期.终身省略不适用于此案例,因此您需要明确写出来.编译器建议'static,这对于hello世界来说很好.如果你有更复杂的生命周期,你需要使用一个生命周期参数; 请参阅下面的示例了解更多信息

你的关闭不能是一个Fn,因为你不能多次调用它.正如你所说的新错误所说,你的闭a包在它被调用时将它捕获的值移出闭包.这与说它是一种self取而代之的方法是一回事&self.如果函数调用是普通方法而不是特殊语法,那么它将是这样的:

trait FnOnce {
    type Output
    fn call(self) -> Output
}

trait Fn : FnOnce {
    fn call(&self) -> Output
}

// generated type
struct MyClosure<T> {
    a: T
}

impl<T> FnOnce for MyClosure<T> {
    fn call(self) -> T { self.a }
}
Run Code Online (Sandbox Code Playgroud)

(这并不比这些类型的实际定义简单得多.)

因此,在短期,消耗其捕获值关闭没有实现Fn,只FnOnce.调用它会消耗关闭.还有一个,FnMut但这里没有关系.

这有另一个含义,就是当它们被移动时消耗它们.您可能已经注意到,您无法调用接受self任何特征对象的方法(Box<T>其中T是特征).要移动对象,移动它的代码需要知道要移动的对象的大小.对于未标注的特征对象,不会发生这种情况.这也适用于Box<FnOnce>.因为调用闭包会移动它(因为调用是一个方法self`),所以你不能调用闭包.

那么如何解决这个问题呢?它Box<FnOnce>有点无用.有两种选择.

如果你可以使用不稳定的Rust,你可以使用FnBox类型:它是一个替代它的FnOnce内部工作Box.它隐藏在功能门后面,因为文档警告你:"请注意,FnBox如果Box<FnOnce()>闭包可以直接使用,将来可能会弃用." 这是一个使用此解决方案的操场,并添加了生命周期参数来修复原始问题.

可能是更广泛适用的工程解决方案的替代方案是避免移出封盖.

  • &'static T如果您始终将静态对象放入闭包中,则可以返回引用.这样你可以根据需要多次调用闭包,并且所有调用者都可以获得对同一对象的引用.

  • 如果对象不是静态的,您可以改为返回Rc<T>.在这种情况下,所有调用者仍然获得对同一对象的引用,并且该对象的生命周期是动态管理的,因此只要需要它就会保持活动状态.这是实现此选项的另一个游乐场.

  • 您可以让闭包将其参数复制到每个调用者.这样可以根据需要多次调用它,每个调用者都可以获得自己的副本.不需要进一步的终身管理.如果以这种方式实现它,您仍然可以使参数变为Rc<T>a而不T是以与上面的选项相同的方式使用该函数.

  • "`T`可以是任何类型,这意味着它*可以*作为参考" - 这解决了我一直很好奇的问题,但从来没有问过.顺便说一句,有没有办法指定某些T*不能*作为参考? (5认同)