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