4

I'm stuck in a similar situation to Borrow-check error with variable not living long enough in nested lambda but am unable to figure out how my situation differs:

let mut vec = vec![vec![0u8, 1u8], vec![2u8, 3u8], vec![4u8, 5u8]];
vec.iter().map(|row| {
    row.iter()
        .map(|d| format!("{:04b}", d))
        .flat_map(|s| s.chars())
        .collect::<Vec<_>>()
});

Which gives the error:

error[E0597]: `s` does not live long enough
 --> src/main.rs:6:35
  |
6 |             .flat_map(|s| s.chars())
  |                           -       ^ `s` dropped here while still borrowed
  |                           |
  |                           borrow occurs here
7 |             .collect::<Vec<_>>()
  |                                - borrowed value needs to live until here

I worked around it by creating a new Vec and appending, but I'm unclear why the first approach did not work.

let mut tmp = vec![];
vec.iter()
    .map(|d| format!("{:04b}", d))
    .for_each(|s| {tmp.append(&mut s.chars().collect::<Vec<_>>());});
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Andrew
  • 13,757
  • 13
  • 66
  • 84
  • When you call `chars()`, you borrow `s` to create a `Char` struct that hold a reference on it. When you exit the closure, s is dropped, and thus `Char` is invalidated. – Boiethios Dec 14 '17 at 21:47
  • @Boiethios This is the exact way that the [documentation](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.flat_map) does it though. Why does it work there and not here? – Andrew Dec 14 '17 at 21:51
  • 1
    @Andrew That example uses static strings. In your example, the strings are created within the iterator, so they don't live long enough. – Sven Marnach Dec 14 '17 at 21:52
  • 1
    See [How can I store a Chars iterator in the same struct as the String it is iterating on?](https://stackoverflow.com/q/43952104/155423) for a version of `.chars()` that would transfer ownership of the `String` and thus work in this case. – Shepmaster Dec 14 '17 at 21:59

1 Answers1

3

You first map the closure |d| format!("{:04b}", d) over your iterator, yielding Strings, which own their data, so this works perfectly find. The flat_map() in the next step calls .chars() on each String. This implicitly derefrences the String to an &str, and creates a Chars iterator referencing this borrow. But now we have a problem – nobody is owning the String that we borrowed anymore.

One workaround is to store a temporary vector of Strings:

let mut vec = vec![vec![0u8, 1u8], vec![2u8, 3u8], vec![4u8, 5u8]];
vec.iter().map(|row| {
    let strings: Vec<_> = row
        .iter()
        .map(|d| format!("{:04b}", d))
        .collect();
    strings
        .iter()
        .flat_map(|s| s.chars())
        .collect::<Vec<_>>()
});

Now we have an owner for the intermediary Strings, and everything works fine again.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • Maybe we should have a consuming version of `chars` for `String` to prevent this kind of boilerplate. – Boiethios Dec 15 '17 at 06:11
  • @Boiethios: I was surprised just yesterday to realize that I could not find a `FromIterator` implementation for that. – Matthieu M. Dec 15 '17 at 13:50
  • @MatthieuM. Seems trivial to do. If this addition does not induce any breaking change, maybe someone (You? I?) should propose something. – Boiethios Dec 15 '17 at 13:56
  • @Boiethios: Please go ahead :) – Matthieu M. Dec 15 '17 at 14:06
  • 1
    Thanks for the explanation. I'm only 2 weeks into rust so still learning ownership. Slowly. So because [chars](https://doc.rust-lang.org/src/alloc/str.rs.html#685) is from the `StrExt` trait which is implemented on `str` and not `String`, it gets converted from `String` to `str`, through `Deref` then passed in to `chars(&self)`? meaning if there was a version of `chars(self)` it would work? – Andrew Dec 15 '17 at 14:53
  • @Andrew Exactly, you summarized perfectly the issue :) – Boiethios Dec 15 '17 at 15:18