简体   繁体   English

Rust函数返回一个闭包:``需要显式生命周期绑定“

[英]Rust function returning a closure: ``explicit lifetime bound required"

The following code does not compile. 以下代码无法编译。

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
    }
}

The error is as following. 错误如下。

11:32 error: explicit lifetime bound required
.../hello/src/main.rs:11 fn bar(x: int) -> (|int| -> int) {
                                            ^~~~~~~~~~~~

I am passing the integer argument to bar by value. 我将整数参数传递给bar by value。 Why does Rust care about the lifetime of an integer passed by value? 为什么Rust关心值传递的整数的生命周期? What is the correct way of writing such a function that returns a closure? 编写返回闭包的函数的正确方法是什么? Thanks. 谢谢。

EDIT 编辑

I found the following in the manual. 我在手册中找到了以下内容。 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.

Is there a further specification of how the compiler infers whether to capture the outer variables by reference, copy them or move them? 是否进一步说明了编译器如何推断是否通过引用捕获外部变量,复制它们还是移动它们? What are the evaluation criteria, and what is their order of application? 什么是评估标准,它们的应用顺序是什么? Is this documented (short of reading the compiler's code)? 这是否有记录(缺少阅读编译器的代码)?

EDIT: I replaced int with i32 , because int is now deprecated. 编辑:我用i32替换了int ,因为int现在已被弃用。 It's been replaced with isize , but that's likely not the correct type. 它已被isize取代,但这可能不是正确的类型。

The compiler is not complaining about the closure's parameter; 编译器没有抱怨闭包的参数; it's complaining about the closure itself. 它抱怨关闭本身。 You need to specify a lifetime for the closure. 您需要为闭包指定生命周期。

fn bar<'a>(x: i32) -> (|i32|:'a -> i32) {
    |n: i32| -> i32 {
        if n < x { return n }

        x
    }
}

But it doesn't work: 但它不起作用:

<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     }
          ...

That's because the closure tries to capture x by reference, but the closure outlives x , which is illegal (in the same way it would be illegal to return a reference to x ). 这是因为闭包尝试通过引用捕获x ,但是闭包超出x ,这是非法的(以同样的方式返回对x的引用是非法的)。

Let's try using a proc . 让我们尝试使用proc A proc captures values by move. proc通过移动捕获值。

EDIT: proc has been removed from the language since this answer was originally written. 编辑: 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
    }
}

Unfortunately, that doesn't work either. 不幸的是,这也不起作用。

<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));
                                                   ^~~

You can only call a proc once. 你只能调用一次proc The call consumes the closure. 该调用消耗了闭包。

The correct solution now is to use "unboxed" closures: 现在正确的解决方案是使用“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
    })
}

Output: 输出:

Trying `foo` with 4: 4
Trying `foo` with 8: 8
Trying `foo` with 13: 8

Is there a further specification of how the compiler infers whether to capture the outer variables by reference, copy them or move them? 是否进一步说明了编译器如何推断是否通过引用捕获外部变量,复制它们还是移动它们? What are the evaluation criteria, and what is their order of application? 什么是评估标准,它们的应用顺序是什么? Is this documented (short of reading the compiler's code)? 这是否有记录(缺少阅读编译器的代码)?

Let me complement Francis' answer: 让我补充弗朗西斯的回答:

Closures like |x| a*x+b 闭包像|x| a*x+b |x| a*x+b are always capturing their surroundings (like a and b here) by reference. |x| a*x+b总是通过引用捕获它们的周围环境(如ab )。 In your case, these are function-local variables and Rust prevents you from returning such a closure because these function-local variables wouldn't exist anymore. 在你的情况下,这些是函数局部变量,Rust阻止你返回这样的闭包,因为这些函数局部变量不再存在。 Let's thank the borrow checker for catching this mistake. 让我们感谢借用检查员抓住这个错误。 The use cases for these closures is typically passing them as parameters to other functions, not returning them. 这些闭包的用例通常将它们作为参数传递给其他函数,而不是返回它们。 However, if you don't access any other variables, such a closure is allowed to outlive the scope of the function it was created in: ||:'static -> SomeType . 但是,如果您不访问任何其他变量,则允许这样的闭包比在其中创建的函数的范围更长: ||:'static -> SomeType The representation of these closures is just a pair of pointers. 这些闭包的表示只是一对指针。 One that points to the function and one that points into the function's stack frame (if something was captures by reference). 一个指向函数,一个指向函数的堆栈帧(如果某些东西是通过引用捕获的)。

Closures you write with proc always capture their surroundings by "acquiring" them (they get moved into the closure object's state). 使用proc编写的闭包总是通过“获取”它们来捕获它们的周围环境(它们会被移动到闭包对象的状态)。 Another property of these kinds of closures is that you can only invoke them once because the associated function actually consumes the closure's state. 这些闭包的另一个属性是你只能调用它们一次,因为相关的函数实际上消耗了闭包的状态。 This is useful for launching concurrent tasks. 这对于启动并发任务很有用。 proc closures incure a heap allocation cost because they store their state indirectly (similar to what Box does). proc闭包会产生堆分配成本,因为它们间接存储它们的状态(类似于Box所做的那样)。 The advantage of this is that the representation of proc closures (ignoring the boxed state) is just a pair of pointers. 这样做的好处是proc闭包的表示(忽略盒装状态)只是一对指针。 One pointer to the function and one pointer to the boxed variables. 一个指向函数的指针和一个指向盒装变量的指针。

Then, there are so-called unboxed closures. 然后,有所谓的无盒装封闭。 As far as I know, unboxed closures are still considered experimental. 据我所知,未装箱的封闭仍然被认为是实验性的。 But they allow you to do exactly what you want -- not directly, but when boxed. 但它们可以让你完全按照自己的意愿行事 - 不是直接,而是装箱时。 Unboxed closures also capture their surroundings by value. 未装箱的封闭装置也可以通过价值捕捉周围环境。 But unlike proc closures, there is no heap allocation involved. 但与proc闭包不同,不涉及堆分配。 They store their variables directly. 他们直接存储变量。 You can think of them as a struct with a unique, unspellable type that implements one of the following traits: Fn , FnMut or FnOnce with a single method that takes its self parameter as &self , &mut self or self respectivly. 您可以将它们视为具有唯一,无法打开的类型的结构,该结构实现以下特征之一: FnFnMutFnOnce ,使用单个方法将其自身参数作为&self&mut selfself respectivly。 Unboxed closures are nice in that they lack the level of indirection for the function and the variables which allows for better inlining and therefore better performance. 无盒装闭包很不错,因为它们缺少函数和变量的间接级别,这样可以实现更好的内联,从而提高性能。 But it's currently not possible to write a function that directly returns such an unboxed closure. 但是目前还不可能编写一个直接返回这种未装箱的闭包的函数。 One solution is to box the unboxed closure like Francis showed in the last section of this answer. 一个解决方案就是像弗朗西斯在本回答的最后一节中所示,打开未装箱的封口。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM