rust-lang / rust-lang/rust

Mixing `'static` and `'this` with iterators or vectors doesn't correctly shorten lifetime to the workable shorter version.

Open
#132,291 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-lifetimes C-bug T-compiler T-types
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

This is either a borrow checker bug, a library bug, or a diagnostics bug. I don't know which one.

I tried this code:

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())
    }

    pub fn columns2<'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 doesn't work either!
        let mut values = vec![];
        values.extend(Entry::leading_columns());
        values.extend(dynamic_columns.into_iter());
        values.extend(Entry::trailing_columns());
        values.into_iter()
    }


    pub fn columns3(&self) -> impl Iterator<Item = String> {
        let mut dynamic_columns = BTreeSet::new();

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

        // Okay, it makes no sense that this doesn't work.
        // * I turned it into String, so it is owned
        // * I collected into a vec and then returned that (which normally works)
        let v: Vec<_> = Entry::leading_columns()
            .chain(dynamic_columns.into_iter())
            .chain(Entry::trailing_columns())
            .map(|v| v.to_string()).collect();
        v.into_iter()
    }
}

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()
    }
}

I expected to see this happen: One of the following:

  • All three cases works, and 'static is shortened to 'this
  • The compiler errors nudge you towards a solution to the issue rather than suggesting incorrect things like adding + 'this

Ideally (if this can't be fixed so that it works) the compiler error should nudge you in the direction that jendrikw suggested on URLO:

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}))

Instead, this happened:

   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[E0521]: borrowed data escapes outside of method
  --> src/lib.rs:27:22
   |
24 |     pub fn columns2<'this>(&'this self) -> impl Iterator<Item = &'this str> {
   |                     -----  ----------- `self` is a reference that is only valid in the method body
   |                     |
   |                     lifetime `'this` defined here
...
27 |         for entry in &self.entries {
   |                      ^^^^^^^^^^^^^
   |                      |
   |                      `self` escapes the method body here
   |                      argument requires that `'this` must outlive `'static`

error[E0521]: borrowed data escapes outside of method
  --> src/lib.rs:43:22
   |
40 |     pub fn columns3(&self) -> impl Iterator<Item = String> {
   |                     -----
   |                     |
   |                     `self` is a reference that is only valid in the method body
   |                     let's call the lifetime of this reference `'1`
...
43 |         for entry in &self.entries {
   |                      ^^^^^^^^^^^^^
   |                      |
   |                      `self` escapes the method body here
   |                      argument requires that `'1` must outlive `'static`

For more information about this error, try `rustc --explain E0521`.
error: could not compile `playground` (lib) due to 3 previous errors
Meta

rustc --version --verbose:

rustc 1.82.0 (f6e511eec 2024-10-15)
binary: rustc
commit-hash: f6e511eec7342f59a25f7c0534f1dbea00d01b14
commit-date: 2024-10-15
host: x86_64-unknown-linux-gnu
release: 1.82.0
LLVM version: 19.1.1
Backtrace

No crash, so not applicable?

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the reproduced example in Playground src/lib.rs and run it using the Rust 1.82.0 version shown in the report. Compare the diagnostics for columns, columns2, and columns3 with the stated expected behavior and the suggested lifetime mapping. Done means the compiler behavior or diagnostics are corrected consistently for these cases.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.