Mixing 'static and 'this in Iterator::chain?

use std::collections::HashMap;
use std::collections::BTreeSet;

struct DataRoot {
    entries: Vec<Entry>
}

impl DataRoot {
    pub fn columns<'this>(&'this self) -> impl Iterator<Item = &'this str> {
        let mut dynamic_columns = BTreeSet::new();

        for entry in &self.entries {
            dynamic_columns.extend(entry.dynamic_coluimns());
        }

        // This combines 'this and 'static, and I want the result to be 'this
        // (which is *shorter*)
        // This doesn't work for some reason, why? And what can be done about it?
        Entry::leading_columns()
            .chain(dynamic_columns.into_iter())
            .chain(Entry::trailing_columns())
    }
}

struct Entry {
    // Various fixed fields here...

    // Other fields that are specific to this case, capture them dynamically
    // #[serde(flatten)]
    other: HashMap<String, String>
}

impl Entry {
    fn leading_columns() -> impl Iterator<Item = &'static str> {
        ["Crate name", "URL", "Maintained", "License", "Std"].into_iter()
    }

    fn dynamic_coluimns(&self) -> impl Iterator<Item = &str> {
        self.other.keys().map(|s| s.as_str())
    }

    fn trailing_columns() -> impl Iterator<Item = &'static str> {
        ["Notes"].into_iter()
    }
}

Playground link:

The error I get is:

   Compiling playground v0.0.1 (/playground)
error: lifetime may not live long enough
  --> src/lib.rs:19:9
   |
9  |       pub fn columns<'this>(&'this self) -> impl Iterator<Item = &'this str> {
   |                      ----- lifetime `'this` defined here
...
19 | /         Entry::leading_columns()
20 | |             .chain(dynamic_columns.into_iter())
21 | |             .chain(Entry::trailing_columns())
   | |_____________________________________________^ returning this value requires that `'this` must outlive `'static`
   |
help: to declare that `impl Iterator<Item = &'this str>` captures data from argument `self`, you can add an explicit `'this` lifetime bound
   |
9  |     pub fn columns<'this>(&'this self) -> impl Iterator<Item = &'this str> + 'this {
   |                                                                            +++++++

error: could not compile `playground` (lib) due to 1 previous error
  • Adding the recommended + 'this does not help.
  • Just returning dynamic_columns.into_iter() does work (as expected).
  • Making leading_columns & trailing_columns instance methods and lying about the lifetimes of the static fields "works", but then I need an actual instance to call them on. :frowning:

Not sure what is going on here or how to fix it properly.

EDIT: Even rewriting it in terms of a temporary vector doesn't work:

        let mut values = vec![];
        values.extend(Entry::leading_columns());
        values.extend(dynamic_columns.into_iter());
        values.extend(Entry::trailing_columns());
        values.into_iter()

This suggests that it isn't just about chain, but some more fundamental limitation of Rust. Is there a way to shorten the lifetime of leading and trailing columns such that this code works?

Even mapping to_string on the &str after the chain doesn't work (but strangely enough doing it before the chaining does work)... That one makes even less sense to me.

I think the 'static requirements comes from the call to leading_columns. Using map, you can shorten the lifetime:

Entry::leading_columns().map(|s: &'static str| -> &'this str {s})
    .chain(dynamic_columns.into_iter())
    .chain(Entry::trailing_columns().map(|s: &'static str| -> &'this str {s}))
2 Likes

Nice! I added this for both leading_columns and trailing_columns and it compiles. So subtyping isn't quite working here, I guess.

Thanks a lot. That is quite a neat fix. I have to say it feels like a hack though, and that it either should just work or Rust diagnostics should push you in the right direction.

Might be worth filing a bug over it. It is at least a diagnostics issue.

EDIT: Went ahead and reported Mixing `'static` and `'this` with iterators or vectors doesn't correctly shorten lifetime to the workable shorter version. · Issue #132291 · rust-lang/rust · GitHub

1 Like

On mobile but my instinct says just diagnostic (and I agree the diagnostic should be improved). Probably there's a type parameter requiring an exact match of types.

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.