Replace elements of a vector as a function of previous values

Hello,

How can I apply a function to every member of a Vec if the Vec is part of a value I don't want to replace?

Suppose I have:

struct A { strings: Vec<String> }

fn transform_string(mut string: String) -> String {
  // somehow transform string
  string.push_str("!");
  string
}

fn main() {
  let strings = vec!["Hello".to_string(), "World".to_string()];
  let a = A { strings };
  // How to apply transform_string() to every member of a.strings?
}

How can I apply my function transform_string() to every member of a.strings?

Thanks!

Do you mean something like:

a.strings = a.strings.into_iter().map(transform_string).collect();
1 Like

Alternatively,

+// Doesn't require ownership
-fn transform_string(mut string: String) -> String {
+fn transform_string(string: &mut String) {
   // somehow transform string
   string.push_str("!");
-  string
 }
let mut a = A { strings };
a.strings.iter_mut().for_each(transform_string);

Generally, .into_iter().map(…).collect() for Vec -> Vec transformations is the correct approach. Rust contributors even managed to put specialization magic into the standard library that can make this transformation happen in-place, for a Vec<S> -> Vec<T> transformation if S and T have identical size and alignment (which is true especially of S and T are the same type).

As for doing this on a Vec contained somewhere, e.g. through &mut Vec<T>, then the code like @jwodder wrote might no longer work directly, the second trick can be to use mem::take (or mem::replace) to gain ownership of the Vec first.

// assume this is all we got for access
let reference: &mut Vec<String> = &mut a.string;
// then this still works
*reference = mem::take(reference).into_iter().map(transform_string).collect();

In case of String, usage of mem::take would have also been an approach to apply the String -> String transformation via &mut String access directly.

let reference: &mut Vec<String> = &mut a.string;
// this works, too
for item in reference {
    *item = transform_string(mem::take(item))
}

There’s a chance that the former code may be cheaper though (even though naively, without the specialization magic I mentioned, the latter seems better. (Note I’ve measured none of this, this is just speculation.) In any case, your example may be a stand-in for values without (cheap!) Default values, anyways, so the former solution is more general.


All this is admittedly still a bit tedious. This is exactly the reason why most String -> String functions are &mut String -> () functions instead; it’s more often than not more convenient / more generally applicable.

Noteworthy to mention would also be the create replace_with offering interesting APIs to apply T -> T functions through a &mut T reference, anyways, but the behavior of this in in the presence of panic means you either risk full abort of the program, or need to provide a way to create Default-like elements, anyways (though they may be less cheap if the replace_with API is used, as their creation is only for panicking cases then).

1 Like

Thank you for all the responses! Unfortunately, I oversimplified my situation, which is more like this:

struct A { strings: Vec<String> }

fn transform_string(mut string: String) -> String {
  // somehow transform string
  string.push_str("!");
  string
}

impl A {
    fn transform_strings(&mut self) {
        self.strings = self.strings.into_iter().map(transform_string).collect();
    }
}

fn main() {
  let strings = vec!["Hello".to_string(), "World".to_string()];
  let mut a = A { strings };
  a.transform_strings();
}

This does not compile:

   Compiling playground v0.0.1 (/playground)
error[E0507]: cannot move out of `self.strings` which is behind a mutable reference
  --> src/main.rs:11:24
   |
11 |         self.strings = self.strings.into_iter().map(transform_string).collect();
   |                        ^^^^^^^^^^^^ ----------- `self.strings` moved due to this method call
   |                        |
   |                        move occurs because `self.strings` has type `Vec<String>`, which does not implement the `Copy` trait
   |
note: `into_iter` takes ownership of the receiver `self`, which moves `self.strings`
  --> /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/core/src/iter/traits/collect.rs:267:18
help: you can `clone` the value and consume it, but this might not be your desired behavior
   |
11 |         self.strings = self.strings.clone().into_iter().map(transform_string).collect();
   |                                    ++++++++

For more information about this error, try `rustc --explain E0507`.
error: could not compile `playground` (bin "playground") due to previous error

How can I fix this? Thanks!

Please give the code that @quinedot posted a try.

Here's what it would look like (this code does compile):

struct A { strings: Vec<String> }

fn transform_string(string: &mut String) {
  // somehow transform string
  string.push_str("!");
}

impl A {
    fn transform_strings(&mut self) {
        self.strings.iter_mut().for_each(|s| transform_string(s));
    }
}

fn main() {
  let strings = vec!["Hello".to_string(), "World".to_string()];
  let mut a = A { strings };
  a.transform_strings();
}

While normally iter() and map() would, we can avoid the conversion to an Iterator and use iter_mut() directly in this case.

iter_mut() produces an IterMut, which happens to have map and for_each (for_each might be more appropriate here, but that's probably a style preference -- I lean towards for_each because we're not producing a new list).

The important difference here is that while iter() produces non-mutable references to the items, iter_mut() produces mutable ones, so you can modify them in place.

Unfortunately in this case the compiler didn't have helpful feedback for you, but it's probably useful to try to get used to the messages and think from the angle of "I want to do Y, but the compiler is trying and failing to do X, why is it trying to do X instead of Y". That might have led you to trying to prevent it from copying in the first place (which usually means you need a mutable reference, not a value). YMMV of course!

2 Likes

If you don't want to change transform_string for some reason,[1] you can use the mem::take approach which @steffahn outlined.

    fn transform_strings(&mut self) {
        self.strings = std::mem::take(&mut self.strings)
            .into_iter()
            .map(transform_string)
            .collect();
    }

  1. hopefully a good one? ↩︎

4 Likes