简体   繁体   中英

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. Why does Rust care about the lifetime of an integer passed by value? 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. It's been replaced with isize , but that's likely not the correct type.

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

Let's try using a proc . A proc captures values by move.

EDIT: proc has been removed from the language since this answer was originally written.

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. The call consumes the closure.

The correct solution now is to use "unboxed" closures:

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 are always capturing their surroundings (like a and b here) by reference. 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. 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 . 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). 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). The advantage of this is that the representation of proc closures (ignoring the boxed state) is just a pair of pointers. 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. 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. 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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