以下代码无法编译.
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
}
}
Run Code Online (Sandbox Code Playgroud)
错误如下.
11:32 error: explicit lifetime bound required
.../hello/src/main.rs:11 fn bar(x: int) -> (|int| -> int) {
^~~~~~~~~~~~
Run Code Online (Sandbox Code Playgroud)
我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.
是否进一步说明了编译器如何推断是否通过引用捕获外部变量,复制它们还是移动它们?什么是评估标准,它们的应用顺序是什么?这是否有记录(缺少阅读编译器的代码)?
Fra*_*gné 10
编辑:我替换int了i32,因为int现在已弃用.它被替换了isize,但这可能不是正确的类型.
编译器没有抱怨闭包的参数; 它抱怨关闭本身.您需要为闭包指定生命周期.
fn bar<'a>(x: i32) -> (|i32|:'a -> i32) {
|n: i32| -> i32 {
if n < x { return n }
x
}
}
Run Code Online (Sandbox Code Playgroud)
但它不起作用:
<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 }
...
Run Code Online (Sandbox Code Playgroud)
这是因为闭包试图x通过引用捕获,但是闭包超出x,这是非法的(以同样的方式返回引用是非法的x).
我们来试试吧proc.A 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
}
}
Run Code Online (Sandbox Code Playgroud)
不幸的是,这也不起作用.
<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));
^~~
Run Code Online (Sandbox Code Playgroud)
你只能打proc一次电话.该调用消耗了闭包.
现在正确的解决方案是使用"unboxed"闭包:
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
})
}
Run Code Online (Sandbox Code Playgroud)
输出:
Trying `foo` with 4: 4
Trying `foo` with 8: 8
Trying `foo` with 13: 8
Run Code Online (Sandbox Code Playgroud)
是否有进一步说明编译器如何推断是通过引用捕获外部变量、复制它们还是移动它们?评估标准是什么,它们的应用顺序是什么?这是否有记录(没有阅读编译器的代码)?
让我补充弗朗西斯的回答:
闭包|x| a*x+b总是通过引用来捕捉他们的周围环境(比如a和b这里)。在您的情况下,这些是函数局部变量,而 Rust 会阻止您返回这样的闭包,因为这些函数局部变量将不再存在。让我们感谢借用检查器发现这个错误。这些闭包的用例通常是将它们作为参数传递给其他函数,而不是返回它们。但是,如果您不访问任何其他变量,则允许这样的闭包比它在以下位置创建的函数的作用域更长:||:'static -> SomeType。这些闭包的表示只是一对指针。一个指向函数,一个指向函数的堆栈帧(如果某些内容是通过引用捕获的)。
你编写的闭包proc总是通过“获取”它们来捕捉它们的周围环境(它们被移动到闭包对象的状态)。这些类型的闭包的另一个特性是你只能调用它们一次,因为关联的函数实际上消耗了闭包的状态。这对于启动并发任务很有用。proc闭包会产生堆分配成本,因为它们间接存储状态(类似于什么Box)。这样做的好处是proc闭包的表示(忽略装箱状态)只是一对指针。一个指向函数的指针和一个指向装箱变量的指针。
然后,有所谓的未装箱的闭包。据我所知,未装箱的闭包仍然被认为是实验性的。但是它们允许您完全按照自己的意愿做事——不是直接做,而是在装箱时做。未装箱的瓶盖也可以通过价值捕捉周围环境。但与proc闭包不同的是,不涉及堆分配。他们直接存储变量。您可以将它们视为具有独特的、无法拼写的类型的结构,该类型实现了以下特征之一:Fn,FnMut或FnOnce具有将其 self 参数设为 的单个方法&self,&mut self或self分别。未装箱的闭包很好,因为它们缺乏函数和变量的间接级别,这允许更好的内联并因此获得更好的性能。但是目前无法编写直接返回这种未装箱闭包的函数。一种解决方案是将未装箱的闭包装箱,如 Francis 在本答案的最后一部分所示。
| 归档时间: |
|
| 查看次数: |
2381 次 |
| 最近记录: |