Cannot move out of mutable reference

Hi, after 30 years of C/C++ coding, I've decided to move to Rust, been coding in it for the past couple weeks, and so far I'm really loving and starting to get a good grasp of the language.

I just hit something however that I cannot for the life of me figure out.
I'm spawning threads from a method and saving the JoinHandles, something like that:

struct Foo {
handles: Vec<JoinHandle<()>>,
}

Somewhere I populate them as such:

self.handles.push(thread::spwan(move || { ..... }));

Everything works perfectly as expected, but then when I try to join the threads, I get an error:

Exact code:

impl Drop for Foo {
pub fn drop_threads(&mut self) {
for th in self.handles {
th.join();
}
}
}

I get this error:
[E0507]: cannot move out of self.handles which is behind a mutable reference

Since I got a mutable reference to self, why can't I modify it ?
I can do
self.handles.clear();
to empty the vector, which works, and baffles me the most.. I can empty the whole vector, but I can't take them out one by one

Thanks in advance for your help!
Jonathan

The for loop tries to move handles so that it can iterate over it, but because you only have a reference to handles (because you only have a reference to self), you can't move it.

There are two ways to fix this, the easy way is to use Vec::drain

impl Drop for Foo {
    pub fn drop_threads(&mut self) {
        for th in self.handles.drain(..) {
            th.join();
        }
    }
}

More generally, you can take the Vec out and replace it with another one using std::mem::replace

It's working fine with drain(), thank you.

I still don't understand why my initial code didn't work however.
Having a mutable reference to self, I should have full access to the members of the struct, no?
The way I see it, by moving a value out of the vector, I'm modifying my own vector.
I can clear the vector, which empties it, with Vec::clear(), so I'm essentially removing all of its content.
If I can remove everything, why can't I move one thing out at a time?

No, references are temporary non-owning pointers. They do not give you access to destroy parts on a value.

In general how would Rust know to clear the Vec? Vec isn't special cased in any way.

As you can see, Vec::clear only requires a reference to a Vec, it does not take ownership of the Vec. This is why you can call it from Drop.

On the insides Vec uses unsafe in order to work. This allows it to do things outside the purview of the borrow checker. But this unsafety is wrapped up in a safe interface, which is why you don't need unsafe to use Vec. This is why Vec can take ownership of the items using only a reference (in order to clear or to implement Vec::drain).

This is what Vec::drain does.

1 Like

Thank you very much for taking the time to respond (and so quickly too!), but it didn't help me understand the problem..

However I just read a few things here and there, and I think I realized my misunderstanding.
When doing:
for th in self.handles

rust is basically doing:
for th in self.handles.into_iter( )

Right ?

If that's the case, I uderstand, as into_iter() takes self and not &mut self as a parameter..
So the Vector itself is consumed, which causes the compilation error..

If I got this right, the problem is not with taking values outside the vector, but destroying the vector itself and leaving "self.handles" invalid.

3 Likes

Yes, this is correct. I think I overlooked explaining the for loop desugarring. Thats something I will need to keep in mind for the future.

2 Likes

Thanks a lot for your help, it is very much appreciated.

To prove to myself my own point I tried this, which confirms my understanding:

while self.handles.len()>0
{
self.handles.remove(0).join();
}

Which basically moves them one by one, without destroying the vector, and works

Yes, and drain() does the same with less copying of elements.

1 Like