0

I am trying to accomplish something rather simple, but not sure how to do it in Rust.

I have a Vec<&Vec>, something like the below example.

[
 ["a1", "b2", "c3"],
 ["d1", "e2", "f3"],
 ["g1", "h2", "i3"]
]

I want to push an additional string at the end of each vector.

[
 ["a1", "b2", "c3", "something"],
 ["d1", "e2", "f3", "something"],
 ["g1", "h2", "i3", "something"]
]

What I've tried so far is below:

vec_of_strings
    .iter_mut()
    .map(|x| x.clone().push("something".to_string()))
    .collect::<Vec<_>>();

println!("{:?}", vec_of_strings);

But the output is showing that nothing is appended.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
kinxiel
  • 793
  • 1
  • 5
  • 7

5 Answers5

5

What you're doing creates a new Vec, it does not modify the exist ones. Indeed, the existing ones cannot be modified as you are borrowing them immutably (the & in Vec<&Vec<_>>).

Note that using .iter_mut() instead of .iter() is pointless here as you aren't mutating the elements.

Additionally, Vec::push() doesn't return anything, so the .to_string() invocation should be giving you a compile-time error. (I assume you meant to call this on the string literal instead.)

Fixing the above issues:

let new_vec_of_strings = vec_of_strings
  .iter()
  .map(|x| {
    let mut x = x.clone();
    x.push("something".to_string());
    x
  })
  .collect::<Vec<_>>();

println!("{:?}", new_vec_of_strings);

However, this all seems like an XY problem -- there is probably a better way to accomplish whatever your goal is.

cdhowie
  • 158,093
  • 24
  • 286
  • 300
  • Thank you very much for the reply. This exactly solves my problem. Still learning and getting used to Rust and as you suggested there is probably a better way to do this, completely agree with that. The way I am trying to solve this problem felt weird. Thanks again. – kinxiel Jun 29 '22 at 02:24
  • There was also an extra `)`, so I assume it's just a typo and fixed it. – Chayim Friedman Jun 29 '22 at 06:18
1

If I understand correctly, You need to return a vec in map.

fn main() {
    let mut vec_of_strings = vec![
        vec!["a1", "b2", "c3"], 
        vec!["d1", "e2", "f3"], 
        vec!["g1", "h2", "i3"]
    ];
    println!("{:?}", vec_of_strings);
    
    let vec_of_strings: Vec<Vec<&str>> = vec_of_strings.iter_mut().map(|x| {x.push("something"); x.clone()}).collect();
    println!("{:?}", vec_of_strings);
}

Rust Playground

Campbell He
  • 297
  • 2
  • 9
1

I think there's a misunderstanding on what the map method was made for. This methodis generally used for data transformations where the original values remain unchanged. What you are really doing here is causing a side effect, and the map method does not help you at all here.

Just use a for loop. It's not like you're saving keystrokes by using map and interators.

However, you mentioned you have a Vec<&Vec>. Having this type seems unfit for your purpose. Cloning the entire vec just to add 1 element is terrible for performance.

I see 2 choices: either have it fully owned, i.e. Vec<Vec>, or just make the inner Vecs mutable, as in Vec<&mut Vec>.

This is the first option, and I think this is the most idiomatic:

fn main() {
    let mut vec_of_strings = vec![
        vec!["a1", "b2", "c3"], 
        vec!["d1", "e2", "f3"], 
        vec!["g1", "h2", "i3"]
    ];
 
    for vec in vec_of_strings.iter_mut() {
        vec.push("something");
    }
    
    println!("{vec_of_strings:?}");
}

If having it in an owned form is not acceptable, then another option is to use Vec<&mut Vec>:

fn main() {
    fn main() {
    let mut vec_of_strings = vec![
        vec!["a1", "b2", "c3"], 
        vec!["d1", "e2", "f3"], 
        vec!["g1", "h2", "i3"]
    ];
    
    //suppose somehow a function gave you this:
    let vec_of_mut_strings: Vec<&mut Vec<_>> = vec_of_strings
        .iter_mut()
        .collect();
    
    for vec in vec_of_mut_strings {
        vec.push("something");
    }
    
    //notice that the original vec_of_strings change
    println!("{vec_of_strings:?}");
}
}
Ricardo Pieper
  • 2,613
  • 3
  • 26
  • 40
  • _"This method is generally used for data transformations where the original values remain unchanged."_ That's not entirely true in Rust. The ability to invoke `.into_iter()` on a collection to consume it, alter the elements (as they are owned), and collect them into a different kind of collection is incredibly powerful. – cdhowie Jun 29 '22 at 04:55
1

I assume your output looks like this:

[(), (), ()]

Here are some suggestions:

  • Prefer using for_each for mutations instead of map:

    vec_of_strings
        .iter_mut()
        .for_each(|x| {
            x.push("something");
        });
    
    println!("{:?}", vec_of_strings);
    

    Note that this assumes that the vec_of_strings is defined similarly as such:

    let mut vec1 = vec!["a1", "b2", "c3"];
    let mut vec2 = vec!["d1", "e2", "f3"];
    let mut vec3 = vec!["g1", "h2", "i3"];
    let mut vec_of_strings: Vec<&mut Vec<&str>> = vec![&mut vec1, &mut vec2, &mut vec3];
    

    Playground

  • The function in .map in your example doesn't return anything because Vec::push doesn't return the vector.

    Separate x to another line to return the vector.

    vec_of_strings.iter_mut()
        .map(|x| {
            x.push("something");
            x
        })
        .collect::<Vec<&str>>();
    

    Note that this assumes that the vec_of_strings is defined similarly as such:

    let mut vec1 = vec!["a1", "b2", "c3"];
    let mut vec2 = vec!["d1", "e2", "f3"];
    let mut vec3 = vec!["g1", "h2", "i3"];
    let mut vec_of_strings: Vec<&mut Vec<&str>> = vec![&mut vec1, &mut vec2, &mut vec3];
    

    Playground

  • You might also like to be more explicit with the type of the vector elements (&str) when using map:

        .collect::<Vec<&str>>();
    

    instead of

        .collect::<Vec<_>>();
    

    which was inferred as Vec<()> by the compiler because of the return type of Vec::push in map (hence the output).

remykarem
  • 2,251
  • 22
  • 28
0

A solution that keeps the original format extending each array by one entry:

vec_of_strings
    .iter_mut()
    .map(|a| {
        let mut v = a.to_vec();
        v.push("something");
        let arr: [&str; 4] = v.try_into().unwrap();
        arr
    })
    .collect::<Vec<[&str; 4]>>();

Playground

Kaplan
  • 2,572
  • 13
  • 14