Modifying items in a mutable vector

I can't quite get the syntax right to be able to modify items in a mutable vector. Here is what I tried.

fn main() {
    let mut colors = vec!["red", "orange", "yellow"];
    for color in &colors {
        let upper = color.to_uppercase();
        *color = &upper;
    }
    println!("{:?}", colors);
}
fn main() {
    let mut colors = vec!["red".to_owned(), "orange".to_owned(), "yellow".to_owned()];
    for color in &mut colors {
        color.make_ascii_uppercase();
    }
    println!("{:?}", colors);
}

The key here is that &mut [T] implements IntoIterator<Item = &mut T>, so you can use it in a for loop. The loop variable color has type &mut String.

The problems with your original code are

  • for color in &colors causes the loop variable to have type &&str (immutable reference to &str) -- because &[T]: IntoIterator<Item = &T> -- which does not allow modifying the Vec entries
  • you can't put a reference to upper into *color (even if you had a mutable reference instead) because upper is dropped at the end of each loop iteration

Another way to do it is to replace for color in &mut colors with for color in colors.iter_mut(), where iter_mut is an inherent method of [T] that returns an iterator over &mut T. iter_mut (and the immutable version iter) are useful in method chains.

2 Likes

To add a little detail to this portion -- this is because your original Vec was a Vec<&str>. The original contents were all &'static str, with the data baked into the binary. The updated contents would have to be stored somewhere (as Strings) at run time for you to put references to them (&'a str) into your Vec. (Or in some static area that you unsafely mutate, but that's not great so let's ignore it here.)

Thus @cole-miller's change to store Vec<String> instead, via .to_owned().

4 Likes

It seems every time I start thinking I'm getting the hang of Rust I get tripped up on another seemingly simple thing. I see your solution works, but I wanted to see if I could avoid typing out .to_owned or .to_string on each color. So I tried this:

fn main() {
    let original = vec!["red", "orange", "yellow", "green", "blue", "purple"];
    let colors = original.iter().map(|c| c.to_string()).collect::<[String]>();

    // Without the turbofish on the previous line I get an error on the next line that says
    // "the element type for this iterator is not specified".
    // I don't understand why the compiler can't infer that it is an Iterator over String values.
    // What else could it be?

    // With the turbofish I get
    // "a value of type `[String]` cannot be built from an iterator over elements of type `String`".
    // It seems like that is exactly what the collect method should do.

    for color in colors {
        color.make_ascii_uppercase();
    }
    
    println!("{:?}", colors);
}

[String] represents a variably-sized contiguous "array" of memory, which in Rust is called a slice. That's it: there is no indirection unless it is explicitly written. For technical reasons, such things / entities cannot be moved around, stored, etc., without indirection. So what you will often see is:

  • a (potentially) shared borrow (reference) to a slice: &'_ [String];

  • an exclusive (thus allowing mutation) borrow (reference) to a slice: &'_ mut [String];

  • an exclusive and owned heap-allocated pointer to a slice: Box<[String]>

  • And more exotic stuff, such as a (potentially) shared and yet owned heap-allocated pointer to a slice (thanks to reference-counting): Arc<[String]> (or Rc as a single-threaded micro-optimization).

In your case, you are collecting / creating something, so you'll be owning it exclusively, thus Box<[String]> is the more apt candidate.

That being said, Box pointers are always fully initialized, which makes it (again, for the same variably-sized reasons (!Sized / not-Sized in Rust parlance)), hard and next to impossible to grow these "arrays" / buffers. For that, there is a more flexible equivalent of Box<[String]>:, which is our good old Vec<String>. Since .collect()ing is an operation that will keep accumulating / aggregating stuff into a buffer, Vec is a more idiomatic type to be using.

Thus, a solution is to do the following:

  fn main() {
      let original = vec!["red", "orange", "yellow", "green", "blue", "purple"];
-     let colors = original.iter().map(|c| c.to_string()).collect::<    [String] >();
+     let colors = original.iter().map(|c| c.to_string()).collect::< Vec<String> >();
2 Likes

Thanks for explaining that! That helps, but I what I really want to do is uppercase a subset of the colors. Here is what I have now:

fn main() {
    let original = vec!["red", "orange", "yellow", "green", "blue", "purple"];
    let colors = original.iter().map(|c| c.to_string()).collect::<Vec<String>>();
    let subset = colors[1..=3]; // error: the size ... cannot be known at compilation time
    for color in subset {
        color.make_ascii_uppercase();
    }
    println!("{:?}", colors);
}

Usually when slicing you need to immediately take a reference to the slice. The error message contains the following help:

help: consider borrowing here
  |
4 |     let subset = &colors[1..=3]; // error: the size ... cannot be known at compilation time
  |                  ^

In your case that would be &mut, not & (because you need to mutate the colors).

2 Likes

Got it! This works:

fn main() {
    let original = vec!["red", "orange", "yellow", "green", "blue", "purple"];
    let mut colors = original.iter().map(|c| c.to_string()).collect::<Vec<String>>();
    let subset = &mut colors[1..=3];
    for color in subset {
        (*color).make_ascii_uppercase();
    }
    println!("{:?}", colors);
}

You can remove the explicit dereference since the auto-deref will take care of that. You never need to derefence in order to call a method unless there's some ambiguity.

1 Like

Just, to reconnect to my previous post: you wanted to iterate and mutate a subset of the slice. To iterate, you did correctly use the for elem in … syntax, but forgot to use indirection, as I just mentioned.

Now, regarding which kind of indirection to use,

  • here you will be borrowing the slice (since you want the Vec to keep existing after the for loop / you don't want the for loop to take ownership of the Vec), so & or &mut it is;

  • and given that you own and thus have exclusive access over the Vector, you can use an exclusive borrow (&mut) to get mutation capabilities, hence the &mut colors[<range>] idiom.

2 Likes