Is it possible to remove this 'static obligation?

[playground]

struct Event;
struct Span;

impl Event {
    fn span(&self) -> &Span {
        &Span
    }
}

impl Span {
    fn fields(&self) -> impl '_ + Iterator<Item = (&'static str, &'_ str)> {
        None.into_iter()
    }
}

fn show_event<'a>(event: &'a Event) {
    show_fields(event.span().fields());
}

fn show_fields<'a, 'data>(fields: impl 'a + Iterator<Item = (&'data str, &'data str)>) {
    for (_name, _value) in fields {
        todo!();
    }
}
slightly less reduced

[playground]

struct Event;
struct Span;

impl Event {
    fn span(&self) -> Option<&Span> { None }
}

impl Span {
    fn parent(&self) -> Option<&Span> { None }
    fn fields(&self) -> impl '_ + Iterator<Item = (&'static str, &'_ str)> { None.into_iter() }
}

fn show_event<'a>(event: &'a Event) -> impl 'a + FnOnce() {
    move || {
        for span in std::iter::successors(event.span(), |span| span.parent()) {
            show_fields(span.fields());
        }
    }
}

fn show_fields<'a, 'data>(
    fields: impl 'a + Iterator<Item = (&'data str, &'data str)>,
) -> impl 'a + FnOnce() {
    move || {
        for (_name, _value) in fields {
            todo!();
        }
    }
}

This currently results in the error

error[E0759]: `event` has lifetime `'a` but it needs to satisfy a `'static` lifetime requirement
  --> src/lib.rs:17:23
   |
16 | fn show_event<'a>(event: &'a Event) {
   |                          --------- this data with lifetime `'a`...
17 |     show_fields(event.span().fields());
   |                 ----- ^^^^
   |                 |
   |                 ...is captured here...
   |
note: ...and is required to live as long as `'static` here
  --> src/lib.rs:17:5
   |
17 |     show_fields(event.span().fields());

as the argument to show_fields seems to gain a 'static obligation. Is there any way to remove the 'static (owned) requirement from the argument here? All of the variations I've attempted retain the 'static obligation.

(related diagnostics issue)

I think your problem is that fields() says it returns an iterator of (&'static str, &'_ str) while the show_fields() function says the iterator must be (&'data str, &'data str).

In theory the compiler should be able to shorten the first element's 'static lifetime to satisfy 'data because you've got immutable references, but instead it seems to be requiring that 'static == 'data.

Variance has always confused me a bit, but maybe when you are working with associated types it flips covariance for contravariance? Similar to what can happen with higher-order functions.

1 Like

This compiles by not using it the same lifetime for both items in tuple

fn show_fields<'a, 'data, 'data2>(fields: impl 'a + Iterator<Item = (&'data str, &'data2 str)>) {
    for (_name, _value) in fields {
        todo!();
    }
}

playground

Assuming it has to do with the 'static propagating because it has the exact same lifetime on both of the tuple items

6 Likes

It couldn't do that, because Iterator<Item = (&'a str, &'a str)> to Iterator<Item = (&'static str, &'static str)> would be unsound. What you're seeing here is invariance. It works if you change the return type of fields to a concrete, covariant type (in this example std::option::IntoIter<(&'static str, &'_ str)>), because rustc is able to shorten the lifetimes of the generic parameters and therefore shorten the lifetime of the Item associated type.

2 Likes

This really is a tricky situation to figure out. So, in summary:

  • Span::fields is returning impl 'a + Iterator<Item = (&'static str, &'a str)>, and
  • show_fields wants impl Iterator<Item = (&'d str, &'d str)>.

Because return position impl Trait is invariant (because it could potentially be fulfilled by a type which is invariant over the lifetime(s)), we fail to unify the two types, because we can't have 'd = 'static and 'd = 'a at the same time.

rustc then chooses to explain the mismatch as "has lifetime 'a but needs to satisfy 'static, which is... not all that illuminating to where the 'static came from, thus the diagnostic issue.

2 Likes

This change also compiles:

-    fields: impl 'a + Iterator<Item = (&'data str, &'data str)>,
+    fields: impl 'a + Iterator<Item = (&'static str, &'data str)>,

Traits are always invariant in their input lifetimes (although this is underdocumented IMO), and associated types are also invariant as well.

Note also that tuple.1 is bound to &self in Span::fields(&self). If you force (&'static str, &'static str), you force &'static self. You can adjust to the signature of Span::fields to avoid equality (express covariance), but so long as 'self: 'tuple_1, it doesn't matter (if 'tuple_1 is 'static, 'self must be 'static too.)

(If 'tuple_1: 'self, it also compiles even when the tuple lifetimes are forced to be equal, but I doubt this is the contract you want on the method.)

Here's how you can make it compile without changing any signatures:

    show_fields(event.span().fields().map(|f| f));
    //                               ^^^^^^^^^^^
6 Likes

FWIW, these hard-to-diagnose issues, are often due to a mismatch between the lifetimes we expected Rust to pick and the ones it actually picked (e.g., we were expecting something like a show_fields::<'a, 'a> call).

In that case, it can be quite helpful to explicitly turbofish the lifetimes as we'd expect them to be provided, so as to get a better error message:

fn show_event<'a>(event: &'a Event) {
-   show_fields(event.span().fields());
+   show_fields::<'a, 'a>(event.span().fields());
}

which yields:

error[E0308]: mismatched types
  --> src/lib.rs:17:5
   |
17 |     show_fields::<'a, 'a>(event.span().fields());
   |     ^^^^^^^^^^^^^^^^^^^^^ lifetime mismatch
   |
   = note: expected tuple `(&'static str, &str)`
              found tuple `(&'a str, &'a str)`

Finally, to go even further, we can also turbofish all the lifetimes there. This way we manage to get a expected tuple `(&'static str, &'a str)` error message: Playground

3 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.