Why does this need 'static lifetime?

Consider the following code snippet.

use futures::{FutureExt, future::{self, BoxFuture}};

fn inject<'a, Env: 'a, A: 'a + Send>(v: A) -> Box<dyn FnOnce(&'a Env) -> BoxFuture<'a, A>> {
    Box::new(move |_| future::ready(v).boxed())
}

This does not compile with the following error (on rust playground stable channel):

   Compiling playground v0.0.1 (/playground)
error[E0310]: the parameter type `A` may not live long enough
 --> src/main.rs:4:5
  |
3 | fn inject<'a, Env: 'a, A: 'a + Send>(v: A) -> Box<dyn FnOnce(&'a Env) -> BoxFuture<'a, A>> {
  |                        -- help: consider adding an explicit lifetime bound...: `A: 'static +`
4 |     Box::new(move |_| future::ready(v).boxed())
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...so that the type `[closure@src/main.rs:4:14: 4:47 v:A]` will meet its required lifetime bounds

error: aborting due to previous error

For more information about this error, try `rustc --explain E0310`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

I have a hard time seeing why A needs the 'static lifetime here. The BoxFuture I'm returning is parameterized by the same lifetime 'a, which means it can only be used within the scope of 'a. And during that same scope, the value v: A is also valid, so why 'static?

Changing the constraint A: 'a + Send into A: 'static + Send does make the code compile, but seems unnecessary to me...

Thanks!

Trait objects default to a 'static lifetime, so this requires that the thing you box outlives 'static. You can change that default by replacing the return type with Box<dyn FnOnce(&'a Env) -> BoxFuture<'a, A> + 'a>, which only requires it to outlive 'a.

2 Likes

Ah... Thanks!

I'm considering it.

Good grief. Every day, just when I start to think I understand something about Rust, somebody posts a question about code like that.

All I see is 'line noise'. A collection of random characters that one might see when the baud rate is set wrong on ones serial port.

I don't mean anything negative saying that. I think I just have to accept the fact that I am never going to live long enough to understand it.

Where would one even look in the book or elsewhere to decode what it is trying to say?

To be very frank, I don't think there's much value to understanding this particular line of code. I only wrote this down because of a use case I had in mind that I want to play around with.

Personally, I find it much more helpful to start with an idea of something I want to do, and progressively refine the types/lifetimes to be as precise as possible.

1 Like

Thanks for that reassurance.

But there is something that bugs me about this kind of thing. There is some kind of "trap door" function going on, between the idea of something to do and the resulting code. Such that one derives the code from the idea pretty easily, but it's much harder for anyone to divine the idea from the code.

By analogy it's like back in the day when a simple idea would result in a page of assembly language source. Kind of easy form me to get from idea to assembler, pretty hard for anyone reading the assembler to see what I'm doing.

A problem that we adopted high level languages to solve.

But don't mind me. My plan, at least for now, is that as soon as I feel the need for a life time tick mark or other such "line noise" I'm going to back off an do something else. I'd like my code to be understandable by my colleagues and others who are not familiar with Rust. Don't want to frighten them away!

2 Likes

It is a bit intimidating, but is really compsed from syntax you’re probably already familiar with. Understanding this sort of thing is a tacit skill that can be learned, bit there’s not a great way to teach it. As you get more familiar with the constituent parts, it gets easier to handle more complicated constructions that include them.

This is a function called inject that takes one parameter:

fn inject<'a, Env: 'a, A: 'a + Send>(v: A) -> Box<dyn FnOnce(&'a Env) -> BoxFuture<'a, A>> {
^^^^^^^^^                           ^^^^^^

The parameter can be any Sendable type:

fn inject<'a, Env: 'a, A: 'a + Send>(v: A) -> Box<dyn FnOnce(&'a Env) -> BoxFuture<'a, A>> 
                       ^^^^^^^^^^^^  ^^^^

It returns a boxed closure:

fn inject<'a, Env: 'a, A: 'a + Send>(v: A) -> Box<dyn FnOnce(&'a Env) -> BoxFuture<'a, A>> 
                                              ^^^^^^^^^^^^^^

The returned closure takes a reference to anything as an argument, that will be valid whenever A is valid, due to the shared lifetime ’a:

fn inject<'a, Env: 'a, A: 'a + Send>(v: A) -> Box<dyn FnOnce(&'a Env) -> BoxFuture<'a, A>>
              ^^^^^^^     ^^                                 ^^^^^^^

The closure returns a boxed future that returns a value of type A that might borrow data from either v or Env:

fn inject<'a, Env: 'a, A: 'a + Send>(v: A) -> Box<dyn FnOnce(&'a Env) -> BoxFuture<'a, A>>
                   ^^     ^^                                             ^^^^^^^^^^^^^^^^

So, before looking at the body of the function, we know that it’s a constructor for some kind of async closure, and the generic type bounds are wide enough that it’s probably not going to be transforming v very much— the only thing that it knows about the vslue is that it can be transmitted between threads.


The function body does indeed generate a boxed closure:

Box::new(move |_| future::ready(v).boxed())
^^^^^^^^^^^^^^^^^

A slightly surprising piece of information is that the closure ignores its argument:

Box::new(move |_| future::ready(v).boxed())
               ^

And instead makes a future that returns v unchanged:

Box::new(move |_| future::ready(v).boxed())
                  ^^^^^^^^^^^^^^^^

I suspect there’s a collection of closures elsewhere in the program that have this particular signature, and this is a helper function to reduce the boilerplate code when the return value is independent of the Env argument that gets provided to every closure when they’re called.

1 Like

Peter Naur wrote an excellent essay on this point tat you might be interested in. He basically argues that the primary value produced by a software development program isn’t the software, but instead the expertise the programmers had to develop in order to write it:

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.