How to fix assignment requires that `'b` must outlive `'a`

Hi everyone.

I faced with an issue related to lifetimes:

error: lifetime may not live long enough
  --> src/features/ui2/modal.rs:37:9
   |
19 | impl<'a> Modal<'a> {
   |      -- lifetime `'a` defined here
...
25 |     pub fn set_items<'b, S1, S2>(mut self, labels: &'b [S1], values: &'b [S2]) -> Self
   |                      -- lifetime `'b` defined here
...
37 |         self.lines = lines;
   |         ^^^^^^^^^^ assignment requires that `'b` must outlive `'a`
   |
   = help: consider adding the following bound: `'b: 'a`

and this is the code of my modal:

type NamedValue<'a> = (&'a str, &'a str);

#[derive(Default)]
pub struct Modal<'a> {
    title: &'a str,
    lines: Vec<Line<'a>>,
    list_state: ListState,
}

impl<'a> Modal<'a> {
    pub fn set_title(mut self, title: &'a str) -> Self {
        self.title = title;
        self
    }

    pub fn set_items<'b, S1, S2>(mut self, labels: &'b [S1], values: &'b [S2]) -> Self
    where
        S1: AsRef<str>,
        S2: AsRef<str>,
    {
        let lines: Vec<Line> = Self::zip(&labels, &values)
            .map(|row| {
                let parts = row.collect::<Vec<_>>();
                Self::build_line(parts)
            })
            .collect();

        self.lines = lines;

        self
    }

    pub fn render(&mut self, frame: &mut Frame, rect: Rect) {
        let layout = &Layout::vertical(
            [Constraint::Percentage(30), Constraint::Percentage(40), Constraint::Percentage(30)]
        ).split(rect);

        let title = Line::from(
            Span::raw(self.title).fg(Color::LightYellow).add_modifier(Modifier::BOLD)
        );

        let instructions = Line::from(vec![
            Span::styled("Navigate: ", Style::new().add_modifier(Modifier::BOLD)),
            Span::styled("<ArrowDn>", Style::new().fg(Color::Blue).add_modifier(Modifier::BOLD)),
            Span::raw(" and "),
            Span::styled("<ArrowUp>", Style::new().fg(Color::Blue).add_modifier(Modifier::BOLD)),
            Span::styled(", select: ", Style::new().add_modifier(Modifier::BOLD)),
            Span::styled("<Enter>", Style::new().fg(Color::Green).add_modifier(Modifier::BOLD)),
        ]);

        let block = Block::bordered()
            .title(title.centered())
            .title_bottom(instructions.centered())
            .border_set(border::ROUNDED);

        let paragraph = Paragraph::new("").centered().block(block);
        frame.render_widget(paragraph, layout[1]);

        let list_items: Vec<ListItem> = self.lines
            .iter()
            .map(|l| ListItem::new(l.clone()))
            .collect();

        let list = List::new(list_items).highlight_symbol(">>>");
        frame.render_stateful_widget(list, layout[1], self.list_state.borrow_mut());
    }

    fn build_line(parts: Vec<NamedValue>) -> Line {
        let mut spans = Vec::new();

        for (label, name) in parts.iter() {
            spans.extend_from_slice(&[
                Span::styled(
                    format!("{}: ", label),
                    Style::default().fg(Color::Magenta).add_modifier(Modifier::BOLD),
                ),
                Span::styled(
                    format!("{}: ", name),
                    Style::default().fg(Color::Green),
                )
            ]);
        }

        Line::from(spans)
    }

    fn zip<'b, S1, S2>(
        labels: &'b [S1],
        values: &'b [S2],
    ) -> impl Iterator<Item=impl Iterator<Item=NamedValue<'b>> + 'b>
    where
        S1: AsRef<str>,
        S2: AsRef<str>,
    {
        values
            .chunks(labels.len())
            .map(move |chunk| {
                chunk
                    .iter()
                    .zip(labels.iter())
                    .map(|(l, v)| (l.as_ref(), v.as_ref()))
            })
    }
}

I added 'b lifetime here because I generate my modal in another part of the code and I pass the data by reference, so this data cannot outlive the modal:

Ok(input) = receiver.recv() => {
      match input {
          // ...
          HandlerOutput::TransferCharactersList(characters) => {
              let char_data = characters
                  .into_iter()
                  .flat_map(|c| vec![c.guid.to_string(), c.name])
                  .collect::<Vec<_>>();

              *modal.lock().await = Modal::default()
                  .set_title("SELECT CHARACTER")
                  .set_items(&["Name", "Guid"], &char_data);
          },
          _ => {},
      }
  }

this modal is stored in mutex because I use it in two tasks: one task to fill the data and one task to render.

And I want to avoid creating extra String (which is obviously the simplest way to solve this issue).

Could somebody help me with fixing lifetimes issue ? And also what is the root cause - it is not clear for me, even if I try to set main lifetime to the items this not help:

pub fn set_items<'b, S1, S2>(mut self, labels: &'b [S1], values: &'b [S2]) -> Self
    where
        S1: AsRef<str>,
        S2: AsRef<str>,
    {
        // I try to set modal's lifetime here explicitly, but this NOT helped
        let lines: Vec<Line<'a>> = ...;

        self.lines = lines;

        self
    }

BTW If I do not use extra 'b lifetime, I got this issue:

error[E0521]: borrowed data escapes outside of async block
  --> src/features/ui2/mod.rs:72:37
   |
41 |         let mut modal = Arc::new(Mutex::new(Modal::default()));
   |             --------- `modal` declared here, outside of the async block body
...
72 |                                     *modal.lock().await = Modal::default()
   |                                     ^^^^^^^^^^^^^^^^^^^ reference to `char_data` escapes the async block body here
73 |                                         .set_title("SELECT CHARACTER")
74 |                                         .set_items(&["Name", "Guid"], &char_data);
   |                                                                       ---------- borrow is only valid in the async block body

Try this:

pub fn set_items<S1, S2>(mut self, labels: &'a [S1], values: &'a [S2]) -> Self

If 'b can be shorter than 'a, you can't assign a Lines<'b> to self.lines, which has type Lines<'a>, and that's what you were trying to do. The compiler's suggestion was to not let 'b be shorter than 'a. But there's no point in accepting lifetimes longer than exactly what you need here, so just use 'a.

Borrow checking is a pass-fail check on your code. Borrow checking cannot change the semantics of code to make it compile.

That's a distinct issue which you should probably ask about in a new topic.

1 Like

Basically 'a and 'b are seperate lifetimes there is nothing that can guarantee that 'a(the modal's lifetime) will outlive 'b(the lifetime of references) which in that case the modal will hold invalid reference to it, so the solution is to gurantee that 'b cannot be shorter than 'a by adding 'b: 'a to your where clause,
it was suggested by the compiler:

As I said, just using the 'a lifetime without extra lifetime leads to the:

error[E0521]: borrowed data escapes outside of async block

Ok, I will create the new topic then.

The new topic: How to fix borrowed data escapes outside of async block

Don't put temporary loans in structs. Having <'a> on a struct means it is not a normal struct, but only a temporary view, and it is forbidden from storing the borrowed data. It will forever be bound to a single scope that it is borrowing from, and there won't be any way to make it live longer. It won't work in any context that requires 'static lifetime (non-scoped threads, tokio tasks). It will create problems with self-referential data.

The correct type for storing strings in structs is String, and &str is a design error.

If you need to store data "by reference" in structs, then Box and Arc are the right reference types. The temporary & is not storing the data, and it forbids the struct from holding this data.

3 Likes

Thank you very much for the explanation !