2016-08-31 2 views
2

У меня есть объект Vec<String>, который делится между потоками через Arc<Mutex>. Я хочу взять его текущее значение и добавить его в другой вектор в другом месте, а затем очистить его:Невозможно добавить вектор в мьютексе другому вектору

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 не будет работать, потому что lines_result фактически MutexGuard объект. Я могу перебирать объект и добавлять каждый элемент, но учитывая, что я очищаю его значения до того, как он выходит из сферы действия, существует ли более эффективный способ?

+0

Вы также можете использовать 'ает :: заменить (& ает * lines_result, Vec :: нового())' для перемещения выведите вектор из-за '& mut' и замените его на пустой. – sellibitze

ответ

8

Вы можете use Vec::drain() переместить все содержимое в итератор, чтобы вы могли использовать его в .extend() и одновременно очищать исходный вектор.

move || { 
    let mut lines_result = lines_clone.lock().unwrap(); 
    store.lines.extend(lines_result.drain(..)); 
} 
7

Вы можете использовать Vec::append(), чтобы переместить все элементы из одного вектора на конец другого.

store.lines.append(&mut lines_clone.lock().unwrap()); 
3

Глядя на ошибку вы получите:

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>>` 

Так как вы говорите, MutexGuard не является Iterator. В большинстве случаев, т. Е. Когда вы вызываете метод на нем, вы можете использовать MutexGuard<T> вместо T, потому что он реализует Deref<T>, и компилятор автоматически разыгрывает, когда это необходимо.

Так один вариант явно разыменовать:

store.lines.extend(&*lines_result); 

Это (частично) работает, потому что &*lines_result является ссылкой на подстилающей Vec, который реализует IntoIterator. Тем не менее, я считаю, что это более идиоматических и ясно, чтобы вызвать метод iter (что эффективно, что компилятор будет делать с IntoIterator:

store.lines.extend(lines_result.iter()); 

Однако, это еще не компилируется, так как это итератор возвращает ссылки на содержимое, . не фактические String s Таким образом, мы могли бы работать вокруг этого, прося их, чтобы быть клонированы тоже:

store.lines.extend(lines_result.iter().cloned()); 

И это работает, но это неэффективно - это выделение новой копии каждого String в Vec, непосредственно перед отбрасывая оригиналы в любом случае. Это (как другой ответ сказал) именно то, что для Vec::drain(); он перемещает некоторые или все элементы Vec в новый итератор, который дает значения (а не ссылки). v.drain(..) (используя полный спектр ..) принимает все из них за один раз, что дает окончательное:

store.lines.extend(lines_result.drain(..));