【问题标题】:The parameter type `T` may not live long enough参数类型“T”的寿命可能不够长
【发布时间】:2018-04-19 10:33:30
【问题描述】:

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

【问题讨论】:

  • 您是否尝试过编译器建议的操作(显式生命周期限制T: 'static)?
  • 另请注意,您不能将变量 (a) 从 Fn 闭包中移出 - 在第一次调用后它将不再存在。
  • 你的目标是什么?
  • 当我把 'static 生命周期我得到这个错误:error[E0507]: cannot move out of captured outer variable in an `Fn` closure
  • 有...各种解决方案,通常表示问题过于宽泛而无法回答。我想是时候让你告诉我们更多关于你的实际目标了 :)

标签: generics rust closures


【解决方案1】:

这里发生了几件事,这都与移动语义和闭包的轻微尴尬有关。

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

您的闭包不能是Fn,因为您不能多次调用它。正如您遇到的新错误所说,您的闭包在调用时将其捕获的值 (a) 移出闭包。这与说它是采用self 而不是&amp;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,但这与这里无关。

这还有另一个含义,与移动时使用的值有关。您可能已经注意到,您不能在任何特征对象上调用采用 self 的方法(Box&lt;T&gt; 其中T 是一个特征)。要移动一个对象,移动它的代码需要知道被移动对象的大小。这不会发生在未调整大小的特征对象上。这也适用于Box&lt;FnOnce&gt;。由于调用闭包会移动它(因为调用是self方法`),所以不能调用闭包。

那么如何解决这个问题呢?它使Box&lt;FnOnce&gt; 有点没用。有两种选择。

如果你可以使用不稳定的 Rust,你可以使用 FnBox 类型:它是 FnOnce 的替代品,在 Box 中工作。它隐藏在功能门之后,因为正如文档警告您的那样:“请注意,如果 Box&lt;FnOnce()&gt; 闭包可以直接使用,则将来可能会弃用 FnBox。” Here's a playground that uses this solution and adds lifetime parameters to fix the original problem.

另一种可能是更广泛适用的工程解决方案是避免搬出封闭区。

  • 如果您总是将静态对象放入闭包中,则可以返回引用 &amp;'static T。这样,您可以随意调用闭包,并且所有调用者都会获得对同一对象的引用。

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

  • 您可以让闭包将其参数复制到每个调用者。这样,它可以根据需要多次调用,并且每个调用者都会得到自己的副本。不需要进一步的生命周期管理。如果您以这种方式实现它,您仍然可以将参数设为Rc&lt;T&gt; 而不是T,以与上述选项相同的方式使用该函数。

【讨论】:

  • "T 可以是任何类型,这意味着它可以作为参考" - 这解决了一个我一直很好奇但一直没有得到解决的问题问。顺便说一句,有什么方法可以指定某些 T 不能 是引用?
  • FnBox 有一个替代方案,它可以稳定运行,但运行时成本很小(我认为FnBox 不会)。 Playground link
【解决方案2】:

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...

因为捕获的变量name 归外部“主”上下文所有,所以它不能被其他人“窃取”。

接下来要尝试的是通过引用传递参数,注意为装箱的Fn trait 定义生命周期。

实现Fn trait 的盒装闭包存在于堆上,必须明确分配正确的生命周期:Fn() -&gt; &amp;'a T` <strong>+ 'a</strong>

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

另一种解决方案是使用 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) -> impl Fn() -> &'a T {
    move || -> &'a T { val }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-12-24
    • 1970-01-01
    • 1970-01-01
    • 2023-02-05
    • 2017-03-09
    • 1970-01-01
    • 2015-06-26
    相关资源
    最近更新 更多