Rust函数返回一个闭包:```明确的生命周期约束"

时间:2014-09-22 05:30:21

标签: rust

以下代码无法编译。

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.

是否有进一步说明编译器如何推断是通过引用捕获外部变量,复制它们还是移动它们?什么是评估标准,它们的应用顺序是什么?这是否有记录(没有阅读编译器的代码)?

2 个答案:

答案 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的引用是非法的)。

让我们尝试使用procproc通过移动捕获值。

编辑: 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这样的闭包总是通过引用捕捉周围环境(例如ab)。在你的情况下,这些是函数局部变量,Rust阻止你返回这样的闭包,因为这些函数局部变量不再存在。让我们感谢借用检查员抓住这个错误。这些闭包的用例通常将它们作为参数传递给其他函数,而不是返回它们。但是,如果您不访问任何其他变量,则允许此类闭包比在||:'static -> SomeType中创建的函数的范围更长。这些闭包的表示只是一对指针。一个指向函数,一个指向函数的堆栈帧(如果某些东西是通过引用捕获的)。

使用proc编写的闭包总是通过“获取”它们来捕获它们的周围环境(它们会被移动到闭包对象的状态)。这些类型的闭包的另一个属性是你只能调用它们一次,因为相关的函数实际上消耗闭包的状态。这对于启动并发任务很有用。 proc闭包会产生堆分配成本,因为它们间接存储它们的状态(类似于Box所做的那样)。这样做的好处是proc闭包的表示(忽略盒装状态)只是一对指针。一个指向函数的指针和一个指向盒装变量的指针。

然后,有所谓的无盒装封闭。据我所知,未装箱的封闭仍然被认为是实验性的。但它们可以让你完全按照自己的意愿行事 - 不是直接,而是装箱时。未装箱的封闭装置也可以通过价值捕捉周围环境。但与proc闭包不同,不涉及堆分配。他们直接存储变量。您可以将它们视为具有唯一,无法打开的类型的结构,该类型实现以下特征之一:FnFnMutFnOnce,其中一个方法将其self参数作为{分别为{1}},&self&mut self。无盒装闭包很不错,因为它们缺少函数和变量的间接级别,这样可以实现更好的内联,从而提高性能。但是目前还不可能编写一个直接返回这种未装箱的闭包的函数。一种解决方案就是像弗朗西斯在本回答的最后部分所示,打开未装箱的封口。