generics - 参数类型 `T` 可能活得不够长

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

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

最佳答案

这里发生了一些事情,而这一切都与围绕移动语义和闭包的轻微尴尬有关。

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

您的闭包不能是 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 但这与这里无关。

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

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

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

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

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

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

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

关于generics - 参数类型 `T` 可能活得不够长,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49918896/

相关文章:

C# 类型推断 : How to properly define constraints on method?

java - 如何创建比较器

rust - 如何在实现特征时对类型施加特征约束?

javascript - Angular2,拖动事件。关闭问题

java - 泛型方法的子类实现

java - 区别只是 List 和 List<Object> 和 List<?>

rust - 检查一个数字是否可以精确表示为 `f32`

docker - 为什么在交叉编译为 musl 时 Rust 文档测试没有在 Docker 中执行?

c# - 迭代器的 Lambda 捕获问题?

Swift - 如何使用闭包在 View 模型中触发函数?