我正在尝试在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
答案 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
,但这里没有关系。
这有另一个含义,就是当它们被移动时消耗它们。您可能已经注意到,您无法调用在任何特征对象self
上Box<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 })
}