How to fix borrowed data escapes outside of async block

Hi everyone !

This topic is related to my previous.

I implemented minimal sandbox similar to my origin code: Rust Playground

The issue is:

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

The code where I got this issue is:

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);
          },
          _ => {},
      }
  }

and my modal code is:

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 want to rid of labels and char_data variables, but my modal lives outside of the scope where this variables are defined.

Could somebody help me to fix the issue ?

Somebody has to own the string data the Modal is borrowing. Right now, nobody does, because after match input {} finishes, input is dropped while the Modal continues to exist. Practically speaking, in this situation, “somebody” means the Modal itself — you have to change it to use owned strings instead of &strs.

The simplest solution is replace all of the &strs with String.

There are more efficient options, which allow for using literals without copying them every time: Cow<'static, str> or arcstr::ArcStr. However, in this case, they are probably not worth using, because the cost of copying an extra few short strings is insignificant relative to the pace at which a user interface operates.

4 Likes

Thank you very much for the explanation.

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.