How can a reference escape a closure? `packet` escapes the closure body here

In the following example, I don t understand why it complains about packet escaping the closure. Yes, packet has a short lifetime, it lives only as long as on_packet function call lasts. However, when I call on_packet_render(&packet), I'm not moving packet, I'm just passing a reference, which will be used and the on_packet_render call will return while I'm inside on_packet, so I don't see why this error.

use std::sync::Arc;
pub type DecoderProvider = Arc<dyn Fn(&dyn Fn(&Option<&Arc<u8>>))>;
trait DecodedPacket<'a, T> {}

fn main() {
    let mut on_packet_render = |packet: &Option<Box<dyn DecodedPacket<u8>>>| {
        if let Some(packet) = packet {
            
        }
    };
    let mut on_packet: Arc<
        dyn for<'c, 'd> FnMut(
            Option<Box<dyn DecodedPacket<'c, u8> + 'd>>,
        )> = Arc::new(|packet| {
        on_packet_render(&packet);
    });
}

Playground

Error:

error[E0521]: borrowed data escapes outside of closure
  --> src/main.rs:15:9
   |
6  |     let mut on_packet_render = |packet: &Option<Box<dyn DecodedPacket<u8>>>| {
   |         -------------------- `on_packet_render` declared here, outside of the closure body
...
14 |         )> = Arc::new(|packet| {
   |                        ------ `packet` is a reference that is only valid in the closure body
15 |         on_packet_render(&packet);
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^ `packet` escapes the closure body here

I think it's just another case of dyn Trait being 'static by default getting in the way. This change allows it to compile:

-    let mut on_packet_render = |packet: &Option<Box<dyn DecodedPacket<u8>>>| {
+    let mut on_packet_render = |packet: &Option<Box<dyn DecodedPacket<u8> + '_>>| {

Box<dyn Trait + '_> is sort of the boxed version of &dyn Trait in that it allows the default 'static bound to be overrode by inference.

shouldn't it not matter? Because I'm passing a reference to an Option, I'm not moving the thing. The only lifetime that should matter should be the one from the Option

I'm not so sure it matters that there are no moves, due admittedly vague intuitions about variance and how lifetime bounds are checked. But while playing with it, the situation isn't how I initially interpreted it either. Namely, the elided dyn DecoderPacket bound is not 'static. Maybe it's implied to be the same as the anonymous lifetime parameter on the trait in this case, but '_ introduces a new and decoupled anonymous lifetime parameter? Unfortunately I have found no clarifying documentation.

The initial error makes it sound like the compiler thinks you may be capturing the passed-in dyn object within a longer-lived object somehow. I haven't been able to suss out a good reason for this (though interior mutability comes to mind, combined with "the API is the contract"), but I haven't been able to convince myself it's a lifetime entanglement issue either.

I also think it has to do with me being able to capture tie value from the Option. This value however has a lifetime, so it couldn't escape.

Anyways, why does Box<dyn Something + 'static> by default? What does it even mean for dyn Something to be 'static? How could a dyn be not 'static? I thought lifetimes were for references. Or does it mean the lifetime of a dyn that can hold a reference?

A dyn Trait consists of a pointer to some other type and a to it's vtable. That other type could be a reference or hold a reference or otherwise involve a lifetime, just like a generic T type parameter can.

Here's a small example.

// `main()` won't compile without the `+ '_`
fn f(d: Box<dyn std::fmt::Display + '_>) {
    println!("{}", d);
}

fn main() {
    let s = "local".to_string();
    let r: &str = &s;
    f(Box::new(r));
}
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.