以下代码无法编译。
fn main() {
let foo = bar(8);
println!("Trying `foo` with 4: {:d}", foo(4));
println!("Trying `foo` with 8: {:d}", foo(8));
println!("Trying `foo` with 13: {:d}", foo(13));
}
//
fn bar(x: int) -> (|int| -> int) {
|n: int| -> int {
if n < x { return n }
x
}
}
错误如下。
11:32 error: explicit lifetime bound required
.../hello/src/main.rs:11 fn bar(x: int) -> (|int| -> int) {
^~~~~~~~~~~~
我按值将整数参数传递给bar
。为什么Rust关心值传递的整数的生命周期?编写返回闭包的函数的正确方法是什么?感谢。
修改
我在手册中找到了以下内容。 In the simplest and least-expensive form (analogous to a || { } expression), the lambda expression captures its environment by reference, effectively borrowing pointers to all outer variables mentioned inside the function. Alternately, the compiler may infer that a lambda expression should copy or move values (depending on their type.) from the environment into the lambda expression's captured environment.
是否有进一步说明编译器如何推断是通过引用捕获外部变量,复制它们还是移动它们?什么是评估标准,它们的应用顺序是什么?这是否有记录(没有阅读编译器的代码)?
答案 0 :(得分:10)
编辑:我将int
替换为i32
,因为int
现已弃用。它已被isize
取代,但这可能不是正确的类型。
编译器没有抱怨闭包的参数;它抱怨关闭本身。您需要为闭包指定生命周期。
fn bar<'a>(x: i32) -> (|i32|:'a -> i32) {
|n: i32| -> i32 {
if n < x { return n }
x
}
}
但它不起作用:
<anon>:13:16: 13:17 error: captured variable `x` does not outlive the enclosing closure
<anon>:13 if n < x { return n }
^
<anon>:11:41: 17:2 note: captured variable is valid for the block at 11:40
<anon>:11 fn bar<'a>(x: i32) -> (|i32|:'a -> i32) {
<anon>:12 |n: i32| -> i32 {
<anon>:13 if n < x { return n }
<anon>:14
<anon>:15 x
<anon>:16 }
...
<anon>:11:41: 17:2 note: closure is valid for the lifetime 'a as defined on the block at 11:40
<anon>:11 fn bar<'a>(x: i32) -> (|i32|:'a -> i32) {
<anon>:12 |n: i32| -> i32 {
<anon>:13 if n < x { return n }
<anon>:14
<anon>:15 x
<anon>:16 }
...
那是因为闭包尝试通过引用捕获x
,但是闭包超过x
,这是非法的(以同样的方式返回对x
的引用是非法的)。
让我们尝试使用proc
。 proc
通过移动捕获值。
编辑: proc
已从该语言中删除,因为此答案最初是在撰写的。
fn main() {
let foo = bar(8);
println!("Trying `foo` with 4: {:d}", foo(4));
println!("Trying `foo` with 8: {:d}", foo(8));
println!("Trying `foo` with 13: {:d}", foo(13));
}
//
fn bar<'a>(x: i32) -> (proc(i32):'a -> i32) {
proc(n: i32) -> i32 {
if n < x { return n }
x
}
}
不幸的是,这也不起作用。
<anon>:5:43: 5:46 error: use of moved value: `foo`
<anon>:5 println!("Trying `foo` with 8: {:d}", foo(8));
^~~
note: in expansion of format_args!
<std macros>:2:23: 2:77 note: expansion site
<std macros>:1:1: 3:2 note: in expansion of println!
<anon>:5:5: 5:51 note: expansion site
<anon>:4:43: 4:46 note: `foo` moved here because it has type `proc(i32) -> i32`, which is non-copyable (perhaps you meant to use clone()?)
<anon>:4 println!("Trying `foo` with 4: {:d}", foo(4));
^~~
您只能拨打proc
一次。该调用消耗了闭包。
现在正确的解决方案是使用“未装箱”的闭包:
fn main() {
let foo = bar(8);
println!("Trying `foo` with 4: {}", foo(4));
println!("Trying `foo` with 8: {}", foo(8));
println!("Trying `foo` with 13: {}", foo(13));
}
//
fn bar(x: i32) -> Box<Fn(i32) -> i32 + 'static> {
Box::new(move |&: n: i32| -> i32 {
if n < x { return n }
x
})
}
输出:
Trying `foo` with 4: 4
Trying `foo` with 8: 8
Trying `foo` with 13: 8
答案 1 :(得分:4)
是否有进一步说明编译器如何推断是通过引用捕获外部变量,复制它们还是移动它们?什么是评估标准,它们的应用顺序是什么?这是否有记录(没有阅读编译器的代码)?
让我补充弗朗西斯的答案:
像|x| a*x+b
这样的闭包总是通过引用捕捉周围环境(例如a
和b
)。在你的情况下,这些是函数局部变量,Rust阻止你返回这样的闭包,因为这些函数局部变量不再存在。让我们感谢借用检查员抓住这个错误。这些闭包的用例通常将它们作为参数传递给其他函数,而不是返回它们。但是,如果您不访问任何其他变量,则允许此类闭包比在||:'static -> SomeType
中创建的函数的范围更长。这些闭包的表示只是一对指针。一个指向函数,一个指向函数的堆栈帧(如果某些东西是通过引用捕获的)。
使用proc
编写的闭包总是通过“获取”它们来捕获它们的周围环境(它们会被移动到闭包对象的状态)。这些类型的闭包的另一个属性是你只能调用它们一次,因为相关的函数实际上消耗闭包的状态。这对于启动并发任务很有用。 proc
闭包会产生堆分配成本,因为它们间接存储它们的状态(类似于Box
所做的那样)。这样做的好处是proc
闭包的表示(忽略盒装状态)只是一对指针。一个指向函数的指针和一个指向盒装变量的指针。
然后,有所谓的无盒装封闭。据我所知,未装箱的封闭仍然被认为是实验性的。但它们可以让你完全按照自己的意愿行事 - 不是直接,而是装箱时。未装箱的封闭装置也可以通过价值捕捉周围环境。但与proc
闭包不同,不涉及堆分配。他们直接存储变量。您可以将它们视为具有唯一,无法打开的类型的结构,该类型实现以下特征之一:Fn
,FnMut
或FnOnce
,其中一个方法将其self参数作为{分别为{1}},&self
或&mut self
。无盒装闭包很不错,因为它们缺少函数和变量的间接级别,这样可以实现更好的内联,从而提高性能。但是目前还不可能编写一个直接返回这种未装箱的闭包的函数。一种解决方案就是像弗朗西斯在本回答的最后部分所示,打开未装箱的封口。