7

Consider the following code example, I have a vector of JoinHandlers in which I need it iterate over to join back to the main thread, however, upon doing so I am getting the error error: cannot move out of borrowed content.

let threads = Arc::new(Mutex::new(Vec::new()));

for _x in 0..100 {
     let handle = thread::spawn(move || {
          //do some work
     }

     threads.lock().unwrap().push((handle));
}

for t in threads.lock().unwrap().iter() {
     t.join();
}
Vladimir Matveev
  • 120,085
  • 34
  • 287
  • 296
Jacob Clark
  • 3,317
  • 5
  • 32
  • 61

3 Answers3

12

Unfortunately, you can't do this directly. When Mutex consumes the data structure you fed to it, you can't get it back by value again. You can only get &mut reference to it, which won't allow moving out of it. So even into_iter() won't work - it needs self argument which it can't get from MutexGuard.

There is a workaround, however. You can use Arc<Mutex<Option<Vec<_>>>> instead of Arc<Mutex<Vec<_>>> and then just take() the value out of the mutex:

for t in threads.lock().unwrap().take().unwrap().into_iter() {
}

Then into_iter() will work just fine as the value is moved into the calling thread.

Of course, you will need to construct the vector and push to it appropriately:

let threads = Arc::new(Mutex::new(Some(Vec::new())));
...
threads.lock().unwrap().as_mut().unwrap().push(handle);

However, the best way is to just drop the Arc<Mutex<..>> layer altogether (of course, if this value is not used from other threads).

Vladimir Matveev
  • 120,085
  • 34
  • 287
  • 296
  • yes, dropping the `Arc>` is the way to go. – oli_obk Jun 01 '15 at 12:31
  • I have tried dropping `Arc>` altogether, and I'm now left with something along the lines of `let mut threads = Vec::new();` `threads.push(handle);` and `for t in threads.iter() {` but I am still seeing the ` error: cannot move out of borrowed content` on `t.join()` – Jacob Clark Jun 01 '15 at 12:32
  • 1
    you need `into_iter()` instead of `iter()` – oli_obk Jun 01 '15 at 12:34
  • 2
    on unstable rust, instead of using the `Option` trick, you can also use the [`split_off`](https://doc.rust-lang.org/nightly/std/vec/struct.Vec.html#method.split_off) function or the [`drain`](https://doc.rust-lang.org/nightly/std/vec/struct.Vec.html#method.drain) function on `Vec` – oli_obk Jun 01 '15 at 12:36
  • Yeah, `drain()` would the most nice solution (except of dropping `Arc>`, of course) if it were stable. – Vladimir Matveev Jun 01 '15 at 12:43
  • 1
    It should also be possible to use simply `mem::swap()` or `mem::replace()` and passing it the reference to the object inside the `Mutex`. This should work on stable rust. – Vaelden Jun 02 '15 at 18:56
  • @Vaelden, you still need to keep some value inside of the mutex, you can't leave it "empty". So at least you need an `Option`, and `Option::take` is just a shortcut for `mem::replace(&mut whatever, None)`. – Vladimir Matveev Jun 02 '15 at 22:59
  • nice hack, any reason why we cannot take value out of the mutex by value and move it out directly? It's possible to do with struct fields when struct is taken by value – let4be May 29 '21 at 19:51
  • @let4be the compiler can track which parts of the struct are moved out precisely because it is available by value. Mutex, being a library-defined struct with opaque implementation and with reference-based accessors, just cannot use the same machinery; it is the same thing as why you can't move out of `&mut T` value without replacing it with something. – Vladimir Matveev Jun 02 '21 at 19:01
6

As referenced in How to take ownership of T from Arc<Mutex<T>>? this is now possible to do without any trickery in Rust using Arc::try_unwrap and Mutex.into_inner()

    let threads = Arc::new(Mutex::new(Vec::new()));

    for _x in 0..100 {
         let handle = thread::spawn(move || {
              println!("{}", _x);
         });

         threads.lock().unwrap().push(handle);
    }

    let threads_unwrapped: Vec<JoinHandle<_>> = Arc::try_unwrap(threads).unwrap().into_inner().unwrap();
    for t in threads_unwrapped.into_iter() {
         t.join().unwrap();
    }

Play around with it in this playground to verify.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=9d5635e7f778bc744d1fb855b92db178

Ethan
  • 1,206
  • 3
  • 21
  • 39
-2

while the drain is a good solution, you can also do the following thing

// with a copy 
let built_words: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
let result: Vec<String> = built_words.lock().unwrap().clone();

// using drain 
let mut locked_result = built_words.lock().unwrap();
let mut result: Vec<String> = vec![];
result.extend(locked_result.drain(..));

I would prefer to clone the data to get the original value. Not sure if it has any performance overhead.

sai umesh
  • 7
  • 8