Lifetimes not shrinking as expected in closures


#1

Consider the following snippet:

struct Struct<'a> {
    reference: &'a i32,
}

trait Trait {
    fn method(&self);
}

impl<'a> Trait for Struct<'a> {
    fn method(&self) {
        println!("{}", self.reference);
    }
}

fn apply<F: FnOnce(&i32)>(function: F) {
    let value = 42;
    function(&value);
}

fn check<T: Trait, F: Fn(&i32) -> T>(make_instance: F) {
    apply(|reference| make_instance(reference).method());
}

fn main() {
    check(|reference| Struct { reference });
}

Apparently, for some reason rustc thinks that Struct created in the closure passed to check should live for the whole activation of check, while I have intended for it to be alive only during the closure passed to apply.

How can one encode it?


#2
error[E0495]: cannot infer an appropriate lifetime for lifetime parameter `'a`
              due to conflicting requirements
  --> src/main.rs:25:23
   |
25 |     check(|reference| Struct { reference });
   |                       ^^^^^^

Any time you see this message, it is helpful to think about what lifetime you expected it to infer for that lifetime parameter. In particular, in this case what is supposed to be the type of T inside check?

Based on the body of main you can tell that T is Struct<'??>… where '?? is the lifetime of whatever the caller of F passes to F. But that is not a real type! It is more like a type constructor where if you feed it a lifetime 't then you get a real type Struct<'t>. The check function requires T to be an actual type though, hence the error.


#3

By writing Struct { reference }, you cause the closure to return Struct<'a> where 'a is the lifetime of the reference: &'a i32. But, as you said, check only promises that the reference is alive for the duration of the closure, so it’s impossible for it to return T = Struct<'a>. In more abstract terms, the lifetime 'a is “escaping” the scope that it was bound to (the scope of the closure). Rust sees the conflicting requirements and complains loudly.


#4

Using a generic associated type :tada: allows you to express that T depends on the caller’s choice of &i32.

trait MakeInstance {
    type Instance<'a>: Trait;
    fn make<'a>(&self, &'a i32) -> Self::Instance<'a>;
}

fn check<M: MakeInstance>(make_instance: M) {
    apply(|reference| make_instance.make(reference).method());
}

fn main() {
    struct MakeStruct;

    impl MakeInstance for MakeStruct {
        type Instance<'a> = Struct<'a>;
        fn make<'a>(&self, reference: &'a i32) -> Struct<'a> {
            Struct { reference }
        }
    }

    check(MakeStruct);
}

But for now, stick with Rc or clone() instead of a reference if possible.


#5

T is Struct<'??>… where '?? is the lifetime of whatever the caller of F passes to F

Yeah, that’s what I intended. I think this diff communicates that to the compiler:

-fn check<T: Trait, F: Fn(&i32) -> T>(make_instance: F) {
+fn check<'a, T: Trait + 'a, F: Fn(&'a i32) -> T>(make_instance: F) {

So now T can be a Struct<'a> where 'a is the lifetime of the reference passed to F.

However, it still fails with a different error about the reference:

error[E0312]: lifetime of reference outlives lifetime of borrowed content...
  --> src/main.rs:21:37
   |
21 |     apply(|reference| make_instance(reference).method());
   |                                     ^^^^^^^^^
   |

which I fail to comprehend :frowning:

Does it mean that reference arguments of closures for some reason have shorter lifetime than the reference to value variable of apply passed to function? Is that what @Fylwind says? But why is that? value should be alive throughout all closure invocations, so I’d expect from all references to it.


#6

The lifetime of reference passed to the closure inside check is not 'a, but rather something the caller “picks” when calling your closure (which happens inside apply in this example).

The new signature that you wrote, fn check<'a, T: Trait + 'a, F: Fn(&'a i32) -> T>(make_instance: F) { has a lifetime 'a selected by the caller of check, and that lifetime does not correspond to the lifetime of reference that’s given to the closure (as mentioned above). As such, the compiler complains because you’re telling it T is valid for 'a.

The issue boils down to needing ATCs, as @dtolnay mentioned earlier. What you’re trying to do, in essence, is ultimately sound, it’s just not possible to express it properly (AFAICT) without ATC support.


#7
error[E0312]: lifetime of reference outlives lifetime of borrowed content...

This error message almost always means Rust is saving you from a legitimate memory safety bug in your code, where if you had expressed the same logic in C++ you would have a use-after-free / segfault / undefined behavior. In this case:

fn check<'a, T: Trait + 'a, F: Fn(&'a i32) -> T>(make_instance: F);

This means the caller of check decides what lifetime 'a is. Suppose they decide they want 'a to be 'static.

fn my_static_make_instance(&'static i32) -> Struct<'static> {
    /* ... */
}

check(my_static_make_instance);

This satisfies all the trait bounds you wrote, because T = Struct<'static> and Struct<'static>: Trait and Struct<'static>: 'static. But clearly apply is going to try to invoke this with a &i32 that is not 'static, and Rust catches this.


#8

You can get away without ATCs here:

trait MakeInstance<'a> {
    type Instance: Trait;
    fn make(&self, &'a i32) -> Self::Instance;
}

fn check<M: for<'a> MakeInstance<'a>>(make_instance: M) {
    apply(|reference| make_instance.make(reference).method());
}

fn main() {
    struct MakeStruct;

    impl<'a> MakeInstance<'a> for MakeStruct {
        type Instance = Struct<'a>;
        fn make(&self, reference: &'a i32) -> Struct<'a> {
            Struct { reference }
        }
    }
    
    check(MakeStruct);
}

#9

Nice! So putting it all together looks like this.


#10

Indeed, good call @Fylwind!

@dtolnay, you can go a step further and do it at the type level entirely (i.e. MakeInstance::make doesn’t need &self): https://play.rust-lang.org/?gist=320b7b67d71e7483846c7e8739e70367&version=stable :slight_smile:


#11

Oh. Now I understand. For some reason I thought that 'a in check's type is some automatically derivable lifetime which can be whatever. However, it is actually a part of a generic type which must be completely defined by the caller of check. The caller has no idea what check is about to do, so it assumes 'a to span at least over the whole invocation of check. While the closure is passed a reference with much shorter lifetime. Hence the error.


#12

@Fylwind, nice! This denotes my intention quite well: check receives something that can make an instance of Trait out of some reference, with Trait’s instance lifetime specified by the reference.

It’s the first time I see this syntax: <M: for<'a> MakeInstance<'a>>. Is this for usage something relatively new? Or just an arcane bit of syntax which does not get much use?


#13

It’s mentioned in the Nomicon: https://doc.rust-lang.org/nomicon/hrtb.html

It’s by no means unsafe. Some might consider it obscure, but it implicitly shows up in lots of places like fn(&str), which is equivalent to for<'a> fn(&'a str).