SweetCoco
SweetCoco

Reputation: 101

How to return a Rust closure that borrows state to use later?

I have a fairly simple bit of code. I have a feeling I need to use a lifetime to accomplish this but I'm stumped right now.

parse_string is a function that accepts a reference to a string, and returns a closure to be used later, here's the code:

fn main() {
    let parse_this = parse_string(&String::from("Hello!"));
    println!("{}", parse_this("goodbye!"));
}

fn parse_string(string: &String) -> impl Fn(&str) -> &String {
    return |targetString| {
        // pretend there is parsing logic
        println!("{}", targetString);
        return string;
    };
}

Compiler error:

error: cannot infer an appropriate lifetime
  --> src/main.rs:7:12
   |
6  |   fn parse_string(string: &String) -> impl Fn(&str) -> &String {
   |                                       ------------------------ this return type evaluates to the `'static` lifetime...
7  |       return |targetString| {
   |  ____________^
8  | |         // pretend there is parsing logic
9  | |         println!("{}", targetString);
10 | |         return string;
11 | |     };
   | |_____^ ...but this borrow...
   |
note: ...can't outlive the anonymous lifetime #1 defined on the function body at 6:1
  --> src/main.rs:6:1
   |
6  | / fn parse_string(string: &String) -> impl Fn(&str) -> &String {
7  | |     return |targetString| {
8  | |         // pretend there is parsing logic
9  | |         println!("{}", targetString);
10 | |         return string;
11 | |     };
12 | | }
   | |_^
help: you can add a constraint to the return type to make it last less than `'static` and match the anonymous lifetime #1 defined on the function body at 6:1
   |
6  | fn parse_string(string: &String) -> impl Fn(&str) -> &String + '_ {
   |                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0312]: lifetime of reference outlives lifetime of borrowed content...
  --> src/main.rs:10:16
   |
10 |         return string;
   |                ^^^^^^
   |
note: ...the reference is valid for the anonymous lifetime #2 defined on the body at 7:12...
  --> src/main.rs:7:12
   |
7  |       return |targetString| {
   |  ____________^
8  | |         // pretend there is parsing logic
9  | |         println!("{}", targetString);
10 | |         return string;
11 | |     };
   | |_____^
note: ...but the borrowed content is only valid for the anonymous lifetime #1 defined on the function body at 6:1
  --> src/main.rs:6:1
   |
6  | / fn parse_string(string: &String) -> impl Fn(&str) -> &String {
7  | |     return |targetString| {
8  | |         // pretend there is parsing logic
9  | |         println!("{}", targetString);
10 | |         return string;
11 | |     };
12 | | }
   | |_^

Upvotes: 3

Views: 885

Answers (2)

Shepmaster
Shepmaster

Reputation: 432169

You have a number of compounding issues:

  1. You need an explicit lifetime to connect the lifetime of the string argument to the lifetime of the return value of the returned closure. Right now, lifetime elision causes it to be inferred the same as the argument to the closure.

  2. You cannot return a reference to the temporary through the function. It needs to be a distinct variable.

  3. You have to move string into the closure to prevent taking another reference to it, which wouldn't live long enough.

Additionally...

  1. targetString should be target_string to follow Rust idioms.
  2. return should not be used at the end of a block to follow Rust idioms.
  3. &str is generally preferred to &String
fn main() {
    let s = String::from("Hello!");
    let parse_this = parse_string(&s);
    println!("{}", parse_this("goodbye!"));
}

fn parse_string<'a>(string: &'a String) -> impl Fn(&str) -> &'a String {
    return move |target_string| {
        // pretend there is parsing logic
        println!("{}", target_string);
        string
    };
}

See also:

Upvotes: 3

trent
trent

Reputation: 28075

You need to add an explicit lifetime annotation to parse_string so that the compiler can tell which lifetimes are the same and which may be different.

Fn(&str) -> &String would be the type for a function that returns a &String of the same lifetime as the &str passed in; i.e., for<'b> Fn(&'b str) -> &'b String. You need to say that the &String returned has the same lifetime as the &String passed in to parse_string:

fn parse_string<'a>(string: &'a String) -> impl Fn(&str) -> &'a String {

Note that Fn(&str) doesn't have a lifetime annotation; this is because the lifetime of the &str passed into the closure is unrelated to the lifetime of the &String passed into parse_string.

In order to make parse_string compile, you need to make one more change. Closures try to borrow their environment if the compiler thinks it doesn't need to be moved. Your closure, which borrows string, can't be returned from the function where string is a local variable. To fix this, you move the captured variable into the closure:

    move |target_string| {
        // pretend there is parsing logic
        println!("{}", target_string);
        string
    }

It's idiomatic in Rust to omit the return in the last expression in a function.

Also note that &String is an unusual type because it offers no expressivity that &str does not provide. It is almost always a mistake to have &String in non-generic code. See Why is it discouraged to accept a reference to a String (&String), Vec (&Vec), or Box (&Box) as a function argument? for more information.

Putting it all together, here's how I'd write parse_string:

fn parse_string<'a>(string: &'a str) -> impl Fn(&str) -> &'a str {
    move |target_string| {
        // pretend there is parsing logic
        println!("{}", target_string);
        string
    }
}

Your main also needs a small tweak: &String::from("Hello!") takes a reference to a temporary String that will be dropped immediately at the end of the line, invalidating the reference. This is easily fixed by storing the String in a variable so it will not be dropped until the end of the scope:

fn main() {
    let hello = String::from("Hello!");
    let parse_this = parse_string(&hello);
    println!("{}", parse_this("goodbye!"));
}

Upvotes: 2

Related Questions