vise
vise

Reputation: 13373

Unable to append a vector in a mutex to another vector

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

Answers (3)

Chris Emerson
Chris Emerson

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 Strings. 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

bluss
bluss

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

kennytm
kennytm

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

Related Questions