Help with understanding vector values ownership within closure

Hi, could you help me with understanding this error and how can I consume my vec with new values after results from closure function?

fn main() {
    let mut errors = vec![];
    let events = vec![("valid", 100), ("invalid", 200), ("valid", 240), ("invalid", 400)];
    
    let result = events.iter().map(move |event| match event {
        ("valid", _offset) => "ok",
        ("invalid", offset) => {
          errors.push(("some_error", offset));
          "ok"
        },
        _ => "ok"
    });
    
    rerender_errors(errors);
    println!("Result: {:?}", result);
}

fn rerender_errors(errors: Vec<(&str, &i16)>) {
    println!("Errors {:?}", errors);
}
error[E0382]: use of moved value: `errors`
  --> src/main.rs:14:21
   |
2  |     let mut errors = vec![];
   |         ---------- move occurs because `errors` has type `Vec<(&str, &i16)>`, which does not implement the `Copy` trait
...
5  |     let result = events.iter().map(move |event| match event {
   |                                    ------------ value moved into closure here
...
8  |           errors.push(("some_error", offset));
   |           ------ variable moved due to use in closure
...
14 |     rerender_errors(errors);
   |                     ^^^^^^ value used here after move

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=9606299158cc1d432431751d25656aa8

There are two problems here:

  • The move in move |event| match event { is telling the compiler that the closure takes ownership of any captured value (in this case only errors). However you don't want it to take ownership because you want to still use it afterwards. The fix is easy, just remove the move keyword.

  • Iterator::map is lazy, it won't do anything until you iterate the resultin iterator using a for loop or the for_each/fold/collect/ecc ecc methods.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=476929483ffe1e81308b3641e419c631

3 Likes

My example started working however in my real world scenario I'd need to pass uncollected iterator so my next function for result would need to get Iterator<T> :hear_no_evil: is there any way to avoid collecting or convert back to iterator after consuming values?

You can't get back the original iterator after consuming its values. You could clone the original iterator before iterating through it, but this is not always possible, might introduce some bugs if the iterator is doing non-trivial work and might also be more expensive than collecting in a Vec.

If you just need an Iterator<Item = T> instead of `Iterator<Item = Result<T, E>> and you're ok with delaying the handling of the errors after iterating through it, then you can use something like itertools::process_results - Rust

1 Like

it seems like I would need both - Vec Result to display errors and I: Iterator<Item = T<'a>> to render results, learned that fold is returning Results for both cases, is it possible to make it working?

fn main() {
    let events = vec![("valid", 100), ("invalid", 200), ("valid", 240), ("invalid", 400)];
    
    let (result, errors) = events.iter().fold((vec![], vec![]), |(mut data, mut err), event| match event {
        ("invalid", offset) => {
          err.push(("some_error", offset));
          (data, err)
        },
        _ => {
          data.push("ok");
          (data, err)
        }
    });

    rerender_errors(errors);
    render_result(result);
}

fn rerender_errors(errors: Vec<(&str, &i16)>) {
    println!("Errors {:?}", errors);
}

//wrong type
// `Vec<&str> is not an iterator`
fn render_result(result: Iterator<Item = &str>) {
    println!("Result: {:?}", result.collect());
}

Either change render_result to take an impl IntoIterator<Item = &str> or change the rerender_errors(errors); call into rerender_errors(errors.iter().copied()); (the copied is there because otherwise the items would have type &&str instead of &str)

1 Like

I just discovered that when I reorder

rerender_errors(errors);
println!("Result: {:?}", result);

to:

println!("Result: {:?}", result);
rerender_errors(errors);

it solves borrow in closure issue and allows to consume results with mapping over events list.
Thank you for explaining Iterator and lazy map, however I still don't fully understand why my fix works.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=9af49f117d82698a58dee60106554547

The error you get in the first case is because when you call render_errors(errors), you move the errors vector to the rerender_errors function, giving up ownership, and any borrow of errors become invalid at that point. But then you try to print result, and result holds a borrow of errors (within the map closure). Because the borrow is invalid at that point, you get the error.

If you instead print result before the function call, you avoid such a "borrow used after move".

In both cases, you still aren't actually iterating your iterator, just printing out it's debug representation.

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.