Reputation: 13373
I have a Vec<String>
object that's being shared between threads via an Arc<Mutex>
. I want to take its current value and append it to another vector elsewhere and then clear it:
use std::thread;
use std::sync::{Arc, Mutex};
struct Store {
lines: Vec<String>
}
fn main() {
let mut store = Store { lines: vec![] };
let lines = Arc::new(Mutex::new(vec!["Initial value".to_string()]));
let lines_clone = lines.clone();
let t2 = thread::spawn(move || {
// populate lines
});
let t1 = thread::spawn(move || {
let mut lines_result = lines_clone.lock().unwrap();
store.lines.extend(lines_result); // This will not work
lines_result.clear();
});
let _ = t1.join();
let _ = t2.join();
}
extend
won't work because lines_result
is actually a MutexGuard
object. I can iterate over the object and append each element, but considering I'm clearing its values before it goes out of scope, is there a more efficient way?
Upvotes: 3
Views: 2558
Reputation: 14021
Looking at the error you get:
error: the trait bound `std::sync::MutexGuard<'_, std::vec::Vec<std::string::String>>: std::iter::Iterator` is not satisfied [--explain E0277]
--> <anon>:20:21
|>
20 |> store.lines.extend(lines_result); // This will not work
|> ^^^^^^
note: `std::sync::MutexGuard<'_, std::vec::Vec<std::string::String>>` is not an iterator; maybe try calling `.iter()` or a similar method
note: required because of the requirements on the impl of `std::iter::IntoIterator` for `std::sync::MutexGuard<'_, std::vec::Vec<std::string::String>>`
So as you say, MutexGuard
isn't an Iterator
. Much of the time, i.e. when you're calling a method on it, you can use a MutexGuard<T>
in place of a T
because it implements Deref<T>
and the compiler will automatically dereference when needed.
So one option is to explicitly dereference:
store.lines.extend(&*lines_result);
This (partly) works because &*lines_result
is a reference to the underlying Vec
, which implements IntoIterator
. However, I think it's more idiomatic and clear to call the iter
method (which is effectively what the compiler will do with IntoIterator
:
store.lines.extend(lines_result.iter());
However, this still doesn't compile as this iterator returns references to the contents, not the actual String
s. So we could work around this by asking for them to be cloned too:
store.lines.extend(lines_result.iter().cloned());
And this works, but is inefficient - it's allocating a new copy of each String
in the Vec
, just before discarding the originals anyway. This is (as another answer has said) exactly what Vec::drain()
is for; it moves some or all of the items of the Vec
into a new iterator which yields the values (not references). v.drain(..)
(using the ..
full range) takes all of them in one go, giving the final:
store.lines.extend(lines_result.drain(..));
Upvotes: 3
Reputation: 13752
You can use Vec::append()
to move all elements from one vector onto the end of another.
store.lines.append(&mut lines_clone.lock().unwrap());
Upvotes: 7
Reputation: 523164
You could use Vec::drain()
to move out all its content into an iterator, so you could use it in .extend()
and at the same time clearing the original vector.
move || {
let mut lines_result = lines_clone.lock().unwrap();
store.lines.extend(lines_result.drain(..));
}
Upvotes: 10