Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lifetime annotation for closure argument

Tags:

rust

lifetime

I'd like to make the following code compile:

struct Provider {}

impl Provider {
    fn get_string<'a>(&'a self) -> &'a str { "this is a string" }
}

fn main() {
    let provider = Provider{};
    let mut vec: Vec<&str> = Vec::new();

    // PROBLEM: how do I say that this reference s here
    // needs to live as long as vec?
    let fun = |s: &str| {
        vec.push(s);
    };

    fun(provider.get_string());
}

Playground link

This is the compile error that I get:

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
 --> src/main.rs:9:22
  |
9 |     let mut vec: Vec<&str> = Vec::new();
  |                      ^^^^
  |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the block at 11:24...
 --> src/main.rs:11:25
  |
11|     let fun = |s: &str| {
  |                         ^
note: ...so that reference does not outlive borrowed content
 --> src/main.rs:12:18
  |
12|         vec.push(s);
  |                  ^
note: but, the lifetime must be valid for the block suffix following statement 2 at 13:6...
 --> src/main.rs:13:7
  |
13|     };
  |       ^
note: ...so that variable is valid at time of its declaration
 --> src/main.rs:11:9
  |
11|     let fun = |s: &str| {
  |         ^^^
like image 822
bennofs Avatar asked Jan 25 '17 11:01

bennofs


2 Answers

Your code works just fine if remove all the lifetime annotations and let the compiler inference do its job:

struct Provider;

impl Provider {
    fn get_string(&self) -> &str { "this is a string" }
}

fn main() {
    let provider = Provider;
    let mut vec = Vec::new();

    let mut fun = |s| {
        vec.push(s);
    };

    fun(provider.get_string());
}

In short, there's no way to explicitly refer to the lifetime of a local variable, only function arguments. The compiler knows how to do it, though.

If you truly needed it, you could create a function to allow annotating the lifetimes:

fn thing<'a>(provider: &'a Provider) -> Vec<&'a str> {
    let mut vec: Vec<&'a str> = Vec::new();

    {
        let mut fun = |s: &'a str| vec.push(s);

        fun(provider.get_string());
    } // End mutable borrow of `vec`

    vec
}

fn main() {
    let provider = Provider;
    thing(&provider);
}

why did the original annotations stop things from working?

Specifically, it's this bit:

let fun = |s: &str| {
    vec.push(s);
};

This declares a new lifetime on the closure. Using a made-up syntax (you can't declare lifetimes on closure arguments), it would be equivalent to:

let fun = <'a> |s: &'a str| {
    vec.push(s);
};

Which is why the compiler has the error:

the lifetime cannot outlive the anonymous lifetime #1 defined on [the closure's block]

There's no connection between that generated lifetime and that of the Provider. Leaving it out allows the compiler to insert the desired but unnamable lifetime.

like image 155
Shepmaster Avatar answered Sep 18 '22 19:09

Shepmaster


Here's a version which compiles:

use std::marker::PhantomData;

struct Provider<'a> {
    _dummy: PhantomData<&'a ()>,
}

impl<'a> Provider<'a> {
    fn get_string(&self) -> &'a str {
        "this is a string"
    }
}

fn f<'b>() {
    let provider = Provider { _dummy: PhantomData };
    let mut vec: Vec<&str> = Vec::new();

    // PROBLEM: how do I say that this reference s here
    // needs to live as long as vec?
    let mut fun = |s: &'b str| { vec.push(s); };

    fun(provider.get_string());
}

fn main() {
    f()
}

Playground link

I made the following changes:

  • Add a lifetime to Provider (I added a PhantomData, but I guess your provider already owns some data it'll provide).
  • Update the get_string method to show that it returns something with the provider's lifetime, not the input lifetime (ie based on the Provider's lifetime parameter).
  • Add a new lifetime parameter 'b to the function (which I renamed to f(), since main() can't have one), which I use to name the lifetime of the closure parameter.

The last one is slightly confusing, as apparently merely adding a name to a lifetime (without apparently adding any constraints) has made it work.

I think (but I'd love some documentation for this) that this is because of lifetime elision. A closure is really a hidden struct with a fn call(&self, s: &str) (in this case) method. According to the lifetime elision rules, the s parameter gains the same lifetime as &self, which is the closure itself. In this case, the closure is declared after vec, so the lifetime is too short. The explicit lifetime means that it is decoupled from the closure's own lifetime.

like image 37
Chris Emerson Avatar answered Sep 19 '22 19:09

Chris Emerson