Пример сценария
struct Foo {
bar: Vec<i32>,
}
let foo = Foo { bar: vec![1, 2, 3] };
let bar: HashMap<i32, i32> = foo.bar.into_iter().map(|x|(x,x)).collect();
Ожидаемое поведение: все значения из foo.bar перемещены в новую хэш-карту, foo.bar теперь является чистым vec и может использоваться после этого, после этого можно использовать foo как всю структуру.
Реальное поведение: foo.bar перемещен как вся коллекция, поэтому вся структура foo теперь перемещена частично и не может быть использована.
Вопрос: почему in_iter потребляет всю коллекцию вместо того, чтобы использовать только элементы и оставлять структуру пустой, и как я могу это обойти?
Почему вызов
into_iter
наVec<T>
потребляет вектор?
pub trait IntoIterator {
type Item;
type IntoIter: Iterator<Item = Self::Item>;
// Required method
fn into_iter(self) -> Self::IntoIter;
}
Vec::into_iter по сути использует эту черту, поэтому имеет смысл только использовать вектор.
Как «взять» какое-то значение и оставить на его месте пустое значение, чтобы избежать частичного перемещения?
Вы можете использовать std::mem::take, который определяется как:
pub fn take<T>(dest: &mut T) -> T
where
T: Default,
Он принимает значение из &mut T
и использует признак Default
для создания на его месте объекта по умолчанию (обычно пустого в случае коллекций). Поскольку Vec
реализует Default
, вы можете использовать его следующим образом:
struct Foo {
bar: Vec<i32>,
}
fn main() {
let mut foo = Foo { bar: vec![1, 2, 3] };
// Take foo.bar so it can be consumed later, and leave an empty vector in its place.
// Note that this requires mutating foo.
let bar = std::mem::take(&mut foo.bar);
let bar: std::collections::HashMap<i32, i32> = bar.into_iter().map(|x| (x, x)).collect();
println!("{bar:?}");
}
В общем, если вы хотите получить какое-то значение, которое не реализует признак Default
, или вы хотите использовать значение, отличное от того, которое оно предоставляет, вы можете использовать std::mem::replace или std::mem:: обмен.
РЕДАКТИРОВАТЬ. Как отметил Иван С в комментариях, вы также можете использовать Vec::drain. Это более специфично для Vec
, но об этом полезно знать, поскольку коллекции в стандартной библиотеке предоставляют «Drain API». Очистка — это процесс взятия некоторых элементов из коллекции (описанных заданным диапазоном) и возврата их в качестве итератора при удалении их из исходной коллекции. Итак, вы также можете написать:
struct Foo {
bar: Vec<i32>,
}
fn main() {
let mut foo = Foo { bar: vec![1, 2, 3] };
// We drain foo.bar, which will result with and emptying it.
// Note that foo still must be mutable
let bar: std::collections::HashMap<i32, i32> = foo.bar.drain(..).map(|x| (x, x)).collect();
println!("{bar:?}");
assert!(foo.bar.is_empty());
}
Передача «полного диапазона» ..
в метод drain
приведет к взятию всех элементов из вектора, оставив его в пустом состоянии.
Однако обратите внимание, что итератор, возвращаемый из метода drain
, заимствует исходный вектор на время его жизни. Это означает, что вы не можете использовать исходный вектор, пока не обработали все его элементы (или не удалили возвращенный итератор). Это, в свою очередь, означает, что если ваш вектор удерживается некоторым Mutex
, то вы должны удлинять критическую секцию, когда удерживаете мьютекс, пока не обработаете все элементы (которые могут быть сколь угодно длинными, в зависимости от того, что вы делаете с этими элементами). Есть также некоторые тонкости, связанные с утечкой памяти и удалением возвращенного итератора, о которых вам следует знать при использовании drain
.
Итак, какой метод вам следует выбрать? Это зависит от того, что вы хотите выразить. Если вы реализуете какую-то очередь перехвата, то замена памяти более четко покажет ваши намерения (и может значительно уменьшить конфликты при блокировках). Однако если вы не берете вектор от «кого-то другого», а просто хотите перебрать его значения, то использование drain
должно быть вполне приемлемым (и даже более идиоматическим).
Спасибо за ответ, просто чтобы уточнить, есть ли у меня data: Arc<Mutex<Vec<Foo>>>
, которым поделились между тредами. Мне нужно это сделать: let mut data=data.lock.unwrap(); let mut data=take(&mut *data); let mut bar: std::collections::HashMap<i32, i32> = data.into_iter().map(|x| (x, x)).collect();
правильно?
@AleksanderKrauze, возможно, ты захочешь упомянуть Vec::drain?
@IvanC хороший улов. Я добавлю это к своему ответу.
Обратите внимание, что drain
, скорее всего, будет иметь лучшую производительность, чем take
+ into_iter
, если вы повторно используете Vec
. Это потому, что take
+ into_iter
освободит память, а это означает, что вектору нужно будет перераспределить при добавлении новых элементов, тогда как drain
позволяет Vec
повторно использовать ту же память. (Как всегда, если производительность имеет значение, проверьте тест, чтобы убедиться)
@KuraiAkoraito Да. Но поскольку
MutexGuard<T>
реализуетDerefMut<Target = T>
, вы можете получить от него&mut Vec
, а затем поменять его на пустой.