Capturing nested lifetimes into impl Trait doesn't work?

The following doesn't compile at the moment:

fn f<'a, 'b>(x: &'a mut &'b String) -> impl FnOnce() + 'a + 'b {
    move || drop(x)
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=8f8046c81dae27d628dfedf4c523fffd

Why is that? Is the code subtly wrong, or is it just a limitation of the borrow checker?

1 Like

This issue may be what you are experiencing:

https://github.com/rust-lang/rust/issues/42940

1 Like

I don't think it is -- that issue is about impl trait capturing lifetimes of generic parameters in scope. In the above example, there are no generic parameters.

+ 'lt means "an owned instance of this type can be used at least for the span 'lt.

Thus, + 'a + 'b means it can be used for the span 'a and the span 'b, i.e., the union.

But your x is constrained not to outlive either of 'a or 'b, it can thus only be used within the intersection.

You'd thus need an intersection operator, which in Rust does not exist but can be expressed with a (not-so-)free parameter:

fn f<'a, 'b, 'inter> (
    x: &'a mut &'b String,
) -> impl FnOnce() + 'inter
where
    'a : /* ⊇ */ 'inter,
    'b : /* ⊇ */ 'inter,
{
    move || drop(x)
}

Note that in this very example, we have 'b ⊇ 'a, and thus we can directly use 'a for 'inter; but if we had x: (Cell<&'a ()>, Cell<&'b ()>) then the 'inter idiom would be necessary.

3 Likes

Yeah, it also seemed to me that perhaps saying just

fn f<'a, 'b>(x: &'a mut &'b String) -> impl FnOnce() + 'a {
    move || drop(x)
}

would be fine, but that fails with hidden type for impl Trait captures lifetime that does not appear in bounds

Yeah that's then the infamous bug tackled by:

  • The TL,DR: is that when Rust sees -> impl FnOnce() + 'a, it tries to define an internal type Ret<'a> = impl FnOnce() + 'a and use -> Ret<'a>, except the real type, &'a mut &'b String, can not be named with 'a exclusively (in the non-mut case, by covariance, &'a &'b String will shrink to &'a &'a String and dodge the issue). So we need to nudge Rust into adding a <'b> parameter for the existential's definition, which is achieved by the Captures<'b> hack.

  • You can notice it's something distinct in that if you Box the closure, and replace impl with dyn, using 'a works :slightly_smiling_face:

2 Likes

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.