Retain a struct instantiated within a closure of fold()

Trying to understand rust and hitting into a some or the other wall each time. Hoping, the frequent posting is not considered spamming. :slight_smile:

Again some code that does not do anything meaningful. Create vector of Person struct and runs a fold on it.

#[derive(Debug)]
pub struct Person {
    first: String,
    last: String,
}


pub fn main() {
    let person_vec = vec![
        Person {
            first: String::from("James"), 
            last: String::from("Croft")
        },
        Person {
            first: String::from("Monica"), 
            last: String::from("Flint")
        }
    ];
    
    person_vec
        .iter()
        .fold(Vec::new(), |mut acc, per| {
            if per.first == "James" {
                acc.push(per);
                return acc
            }
            let roma = Person {
                first: String::from("Roma"), 
                last: per.last.clone()
            };
            acc.push(&roma);
            acc
        });
}

It fails with -

   Compiling playground v0.0.1 (/playground)
error[E0597]: `roma` does not live long enough
  --> src/main.rs:31:22
   |
31 |             acc.push(&roma);
   |                      ^^^^^ borrowed value does not live long enough
32 |             acc
33 |         });
   |         - `roma` dropped here while still borrowed

error: aborting due to previous error

The error is pretty clear that roma will go out scope after the call to the anonymous function is done and the reference push(ed) to the acc will be invalid.

Question: How to work around this situation? Implement a copy trait? What if the struct is large enough that copy(ing) is expensive?

I happened to do all the typing and then realised that I could use into_iter instead of iter and push(roma) instead of push(&roma).

Nevertheless, it would be good to get thoughts from the members here. :slight_smile:

I prefer using collect (playground) over fold, it does pretty much the same thing (in this case) but I like it better.

To answer your question, you could use an enum, a variant for borrowed values and the other for owned ones. playground

Also .collect() utilizes Iterator::size_hint() so it will outperforms .fold() with Vec::new() in many cases.

1 Like

In the example you have used map() which returns a new item for every item in the collection. What if the operation (the code sample I posted does not make it is very helpful here) is that of fold - where we are more likely collapsing n values in the collection to m values where, typically, m < n?

There are ways to do it, which one is "the best" will depend on the actual case.
You can use for example a filter (playground) it's easy to use but it has some limits.
You can also return an Option and flatten the iterator (playground) it's more flexible but also a little more complex.
That's just 2 ways to go from n to m elements, I'd need some code to help more =)

Edit: playground n°2 using filter_map instead

1 Like

If your mapping to Option and flattening you can use filter_map instead.

1 Like

Might be a good clippy lint don't you think?

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.