Difference of '_ vs for<'a>

On this example:

use std::sync::Arc;

trait DecodedPacket<'a> {}

struct ReferencePacket<'a> {
    data: &'a [u8],
}

impl<'a> DecodedPacket<'a> for ReferencePacket<'a> {}

struct Decoder {}

impl Decoder {
    pub fn receive<'a, 'b>(&self, on_packet: Arc<dyn Fn(&'b Box<dyn DecodedPacket<'a>>)>) {
        let slice: &[u8] = &[0, 1, 2];
        let reference_packet: Box<dyn DecodedPacket<'a>> =
            Box::new(ReferencePacket { data: slice });

        on_packet(&reference_packet);
    }
}

Playground

I get:

error[E0759]: `on_packet` has lifetime `'a` but it needs to satisfy a `'static` lifetime requirement
  --> src/lib.rs:17:13
   |
14 |     pub fn receive<'a, 'b>(&self, on_packet: Arc<dyn Fn(&'b Box<dyn DecodedPacket<'a>>)>) {
   |                                              ------------------------------------------- this data with lifetime `'a`...
...
17 |             Box::new(ReferencePacket { data: slice });
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...is captured here...
18 | 
19 |         on_packet(&reference_packet);
   |                   ----------------- ...and is required to live as long as `'static` here

I don't see any rule that specifies on_packet should only accept 'static lifetimes. I specifically made is such that 'a is parametrized in the function, so it should be whatever the caller decides.

it looks like the problem is that the caller specifies the lifetimes, so since it doesn't know which Arc I will pass, it prepares the function for the worst case possible which would be an Arc with 'a and 'b being 'static . Am I right?

Why is it that doing on_packet: Arc<dyn Fn(&dyn DecodedPacket<'_>)> fixes the problem?

Your intuition is in a sense correct. But that problem is more with 'b than 'a (with an important caveat). If you click the error link, you will see that dyn Trait has an implicit lifetime that defaults to 'static. This is being applied to the dyn DecoderPacket inside a Box in on_packet. If you make this change:

-        on_packet: Arc<dyn Fn(&'b Box<dyn DecodedPacket<'a>>)>,
+        on_packet: Arc<dyn Fn(&'b Box<dyn DecodedPacket<'a> + 'a>)>,

You will see the error about 'b.

If we then say the dyn Fn must accept a reference of any lifetime, not and not some specific lifetime chosen by the caller of receive:

-    pub fn receive<'a, 'b>(
+    pub fn receive<'a>(
        &self,
-        on_packet: Arc<dyn Fn(&'b Box<dyn DecodedPacket<'a> + 'a>)>,
+        on_packet: Arc<dyn Fn(&Box<dyn DecodedPacket<'a> + 'a>)>,

Then the compiler is satisfied even though the parameter 'a is chosen by the caller of receive. However, this still may not be good enough for your actual use case. I'll return to that later.

Now the dyn Fn has to be able to take a reference of any lifetime to a DecoderPacket of any lifetime; the caller of the dyn Fn chooses them. So in this code, receive chooses them. It chooses some lifetime that fits within the body of receive, so there are no lifetime conflicts when, say, the Box is dropped.

Before this change, receive didn't have that choice. You said the caller of receive chooses 'a and 'b, and those have to match up with the lifetimes in on_packet.


OK, now let's revisit my last playground link, where 'a is still chosen by the caller of receive. I alluded to a caveat a couple of times. Let me make what may seem like an inconsequential change:

-        let slice: &[u8] = &[0, 1, 2];
+        let array = [0, 1, 2];
+        let slice: &[u8] = &array;

Now we're back to 'a being a problem. What's going on?

It's because your unnamed array that you used to make your slice was promoted into a const static that you could take a 'static (or arbitrary 'a) reference to. By binding the array locally, it must be dropped at the end of the function.

You said you parameterized 'a on purpose, so this may pose a problem for you, if you're not actually going to use data that can be promoted. (I'm not sure from your example; maybe there are other possibilities like adjusting the DecodedPacket trait too.)

It's also not a problem if the caller of receive doesn't choose 'a.

3 Likes

Let me throw out one more scenario which compiles, wherein

  • The dyn DecoderPacket keeps the implicit 'static bound
  • receive can keep the 'a parameter
  • But the array has to be promoted to a static still
  • And the trait impl has to be expanded

The changes from the last working version I presented are:

-        on_packet: Arc<dyn Fn(&Box<dyn DecodedPacket<'a> + 'a>)>,
+        on_packet: Arc<dyn Fn(&Box<dyn DecodedPacket<'a>>)>,

And

-impl<'a> DecodedPacket<'a> for ReferencePacket<'a> {
+impl<'a: 'b, 'b> DecodedPacket<'b> for ReferencePacket<'a> {

Playground.

Here, I'm saying that a ReferencePacket<'a> can do DecodedPacket<'b> things for any 'b shorter than (or equal to) 'a. This seems like it could make sense to me, but the trait was empty in your example and I have no idea if it actually fits your use case.

Then variance/lifetime shortening kick in because the lifetimes of the the dyn DecoderPacket and the ReferencePacket you create from the array are no longer forced to be equal by the trait impl.


To be quite honest, I'm still not 100% solid on

  • the mechanics at work in this particular case
  • what the language about the Arc being captured by the Box in the errors is about
  • what compromises are involved by between having the 'a or 'static bound on the boxed dyn DecodedPacket

I'm hoping someone else on the forum chimes in.

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.