Rustlings, move_semantics2.rs, mutability, ownership

Hello! So, I'm fighting with the borrow checker. At some point, after following the guidance of the compiler, I arrive to some compiling solution:

fn main() {
    let mut vec0 = Vec::new();

    let mut vec1 = fill_vec(&mut vec0);

    // Do not change the following line!
    println!("{} has length {} content `{:?}`", "vec0", vec0.len(), vec0);

    vec1.push(88);

    println!("{} has length {} content `{:?}`", "vec1", vec1.len(), vec1);
}

fn fill_vec(vec: &mut Vec<i32>) -> Vec<i32> {
    // let mut vec = vec;

    vec.push(22);
    vec.push(44);
    vec.push(66);

    vec.to_vec()
}

My question is: what's going on here? Somehow it works, but it's different from the solutions I see online. I'm also struggling to see the problem in the first place. This is what I understood:

  1. The vec0 was moved into fill_vec, so the ownership was taken by fill_vec
  2. This happens because Vec is not a primitive and it doesn't get copied at compile-time automatically.
  3. We're trying to use vec0 (is it the same as borrowing, as the compiler says?) after that, but we cannot because the function doesn't own it anymore
  4. So, what do I do? I try to pass only the reference, so I modify it like this
let mut vec1 = fill_vec(&vec0);
  ...
fn fill_vec(vec: &Vec<i32>) -> Vec<i32> {
  1. The compiler tells me wrong type, so I add vec.to_vec(), but why can't I simply do *vec ?
  2. Then: help: consider changing this to be a mutable reference: &mut Vec<i32>
  3. At this point I'm lost, I made vec0 mutable and suddenly it works.
  • But why does it need to be mutable?
  • why if it is immutable, I need to clone it inside fill_vec?
  • what would be the best solution in this case, if I want to keep it maintainable and at the same time performant? that also mean, I don't like to duplicate memory usage but I also don't like stuff being mutable.

Correct

Since inside of the function, vec.push(...) is called, which is a (&mut self)-method for Vec, there needs to be a vector that you can actually mutate. This can either be the original vector vec0 that's been moved into fill_vec, as would be the case if you keep the argument (vec: Vec<i32>) (but of course, this approach runs into problems when the main function tries to use vec0 another time later; or it could be the original vector vec0, which is still owned by the main function, and only borrowed by fill_vec; however it would need to be borrowed mutably, because the push call still needs mutable access. Or it could be an owned copy of the original vec0; this could be achieved in two ways: either you clone the Vec in the main function, and pass the clone, or you pass a reference (this time it can be a shared reference) and the first thing that fill_vec does is clone the vector.

Note that .to_vec on a &Vec<T> or a &mut Vec<T> will essentially just clone the whole vector. Cloning turns a borrowed &Vec<T> into a new owned copy (of type Vec<T>). (To explain why this method exists at all, it's actually a method on slices, so that you can turn &[T] into a Vec<T> by cloning all the elements.)


I'm not 100% certain what clone you have in mind, i.e. where in the code exactly would you clone? Assuming it's the same thing as I suggested above, i.e. the first thing that fill_vec does is cloning the vector, then it's necessary for the reason I've explained above: push needs mutable access to some vector, which means you need fill_vec to own one itself (and cloning an (immutably) borrowed vector is a way to obtain ownership (of the clone)), or to have mutable access to a vector that's still owned by the main function.


The "best" solution depends a bit on the actual use case; the code in this example is more of a toy example. In general, cloning the whole vector is kind-of expensive (though in this concrete case again, cloning an empty vector is really cheap); so in practical Rust code, you should clone vectors when you need multiple independent owned values that can me mutated independently and such; whereas when you don't need those, you can try to avoid cloning.

Trying to avoid the to_vec here is not too hard. Since the fill_vec(vec: &mut Vec<i32>) with a &mut ... argument makes it return value basically already accessible in the function argument (by mutating it), you don't really need to return a copy of the whole thing anymore. Hence, if you skip the -> Vec<i32> return value, you can skip the vec.to_vec() accordingly; you won't get a vec1 back then of course, so also skip the let mut vec1 = part of the call, and use vec0 throughout the remaining main function.

This last suggestion is in line with the last suggestion that rustlings hint move_semantics2 should be giving you:

So vec0 is being moved into the function fill_vec when we call it on line 10, which means it gets dropped at the end of fill_vec, which means we can't use vec0 again on line 13 (or anywhere else in main after the fill_vec call for that matter). We could fix this in a few ways, try them all!

  1. Make another, separate version of the data that's in vec0 and pass that to fill_vec instead.
  2. Make fill_vec borrow its argument instead of taking ownership of it, and then copy the data within the function in order to return an owned
    Vec<i32>
  3. Make fill_vec mutably borrow its argument (which will need to be mutable), modify it directly, then not return anything. Then you can get rid of vec1 entirely -- note that this will change what gets printed by the first println!

As far as I can tell, reading these hints - even after solving the exercise without needing a hint - can be a good idea in general, because they can sometimes provide further references or extra tasks, as the one above that suggests "a few ways" and encourages you to "try them all".

clear and exhaustive, thank you!

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.