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

时间:2018-04-19 10:33:30

标签: generics rust closures

我正在尝试在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
    })
}

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

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 | |         })
   | |__________^

我已尝试按错误建议添加显式生命周期绑定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

2 个答案:

答案 0 :(得分: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 }
}

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

简而言之,一个使用其捕获值的闭包不实现Fn,只实现FnOnce。调用它会消耗关闭。还有一个FnMut,但这里没有关系。

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

那么如何解决这个问题呢?它使FnOnce有点无用。有两种选择。

如果您可以使用不稳定的Rust,则可以使用FnBox类型:它可以替代在Box内使用的FnBox。它隐藏在功能门后面,因为文档会警告您:“请注意,如果Box<FnOnce()>闭包可以直接使用,将来可能会弃用&'static T。” Here's a playground that uses this solution and adds lifetime parameters to fix the original problem.

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

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

  • 如果对象不是静态的,您可以改为返回Rc<T>。在这种情况下,所有调用者仍然获得对同一对象的引用,并且该对象的生命周期是动态管理的,因此只要需要它就会保持活动状态。 Here's another playground implementing this option.

  • 您可以让闭包将其参数复制到每个调用者。这样可以根据需要多次调用它,每个调用者都可以获得自己的副本。不需要进一步的终身管理。如果您以这种方式实现它,您仍然可以将参数设为T而不是{{1}},以便使用与上述选项相同的功能。

答案 1 :(得分:0)

const i = "0"; document.querySelectorAll(`rect[data-r='${i}'`); 函数返回一个对返回的类型simple通用的闭包。

这意味着返回的类型可以是任何内容,例如引用或包含引用的类型,因此编译器建议在类型上指定T

'static

但现在你遇到了问题:

fn simple<T: 'static>(a: T) -> Box<Fn() -> T> {
    Box::new(move || -> T { a })
}

因为捕获的变量error[E0507]: cannot move out of captured outer variable in an `Fn` closure --> src/main.rs:2:29 | 1 | fn simple<T: 'static>(a: T) -> Box<Fn() -> T> { | - captured outer variable 2 | Box::new(move || -> T { a }) | ^ cannot move out of captured outer variable in an `Fn` closure error[E0597]: `name` does not live long enough --> src/main.rs:7:24 | 7 | let test = simple(&name); | ^^^^ borrowed value does not live long enough 8 | println!("Hello {}!", test()) 9 | } | - borrowed value only lives until here | = note: borrowed value must be valid for the static lifetime... 归外部&#34; main&#34;所有。背景,它不能被偷走&#34;由别人。

接下来要尝试的是通过引用传递参数,注意定义盒装name特征的生命周期。

实现Fn特征的盒装闭包存在于堆上,并且必须明确指定正确的生命周期:Fn

Fn() -> &'a T` + 'a

另一个解决方案是使用impl trait,从Rust 1.26开始提供:

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

fn simple<'a, T: 'a>(val: &'a T) -> Box<Fn() -> &'a T + 'a> {
    Box::new(move || -> &'a T { val })
}