GAT lifetime error for rustc <1.69

I recently refactored my program to use GATs (supported from rustc >=1.65). Now, I have the problem that my code does not compile with rustc <1.69. I refactored my original problem, in particular this code line, to a minimal example:

trait DataT: 'static {
    type V<'a>: Clone;
}

fn box_once<'a, T: 'a>(x: T) -> Box<dyn Iterator<Item = T> + 'a> {
    Box::new(core::iter::once(x))
}

fn works<'a, V: Clone + 'a>(y: V) {
    || box_once(y.clone());
}

fn fails<'a, D: DataT>(y: D::V<'a>) {
    || box_once(y.clone());
}

The error message:

$ cargo +1.65 check --quiet
error: `<D as DataT>::V<'_>` does not live long enough
  --> src/main.rs:17:8
   |
17 |     || box_once(y.clone());
   |        ^^^^^^^^^^^^^^^^^^^

Starting from rustc 1.69, this compiles fine.

Removing either ||, box_once, or .clone() from fails() makes the program compile. But in my actual code, I need all three of these. I thought that maybe I could generalise the signature for box_once a bit, but I wasn't able to.

Do you have any ideas how to adapt my code to compile for rustc 1.65?

[EDIT]
I just found Generic associated types to be stable in Rust 1.65 | Rust Blog, which mentions limitations in initial GAT support with lifetime errors that eerily resemble mine. Could it be that these just cannot be fixed in early Rust versions?

I don't think there's a workaround for this. You can't even transmute the lifetime away in this case.

Note that over 99.4% of requests to crates.io are from 1.70 or newer.

3 Likes

Try annotating the call completely.

    || box_once::<D::V<'a>>(y.clone());

I tried your suggestion, but it unfortunately it did not have any effect in my actual code ...
Thanks for having a look at it nonetheless, @quinedot.

1 Like

OK, then I think I'll bite the bullet and just increase MSRV to 1.69. Thanks for the pretty statistics, by the way! :slight_smile:

I have experimented a bit with your repo, and yeah, you're basically constantly running into this <1.69.0 bug which amounts to:

  • if you ever involve an extra trait bound on a GAT (maybe in a higher-ranked context, I don't remember all the specifics of that time right now);
  • then within the body of such a constrained function, type inference shall be unable to properly infer occurrences of that GAT. So every. F*cken. Generic. Function. Call. needs to be properly turbofished and be provided any GAT (such as your D::V<'a>) or wrapper thereof (such as Exn<D::V<'a>>).
    For instance, your box_once() calls, in your real code, needed to be using:
call_once::<Result<D::V<'a>, Exn<D::V<'a>>>>(…)

But you had just so many (normal, natural) layers of functions around to make this ever so remotely practical:

error: `<D as DataT>::V<'_>` does not live long enough                                                                                  ▐
    --> jaq-core/src/filter.rs:686:78                                                                                                       ▐
     |                                                                                                                                      ▐
 686 |                     box_once::<ResultV<'a, D>>(paths.try_fold(v, |acc, path| path?.update(acc, &f)))                                 ▐
     |                                                                              ^^^^^^^^^^^^^^^^^^^^^

All in all, this is a serious usability-restraining bug we had in those versions, and the workaround (of just Turbofishing Everything™) does indeed not scale, so a toolchain update is indeed the best course of action :slightly_smiling_face:

3 Likes

Oh god, sorry for having dragged you into this experiment. :scream:
Anyway, Mr. Turbofish has deserved some rest, which Rust 1.69 has provided.

These little things make you grateful for all the work that has gone into the compiler.

Thanks for your detailed analysis of the bug!

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.