Почему Rust In_it перемещает всю коллекцию, а не очищает ее

Пример сценария

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 потребляет всю коллекцию вместо того, чтобы использовать только элементы и оставлять структуру пустой, и как я могу это обойти?

Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
2
0
51
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Почему вызов into_iter на Vec<T> потребляет вектор?

Это потому, что черта IntoIterator определена для потребления значения, превращенного в итератор (по соглашению «в» в Rust означает, что вы меняете что-то на что-то другое, если вы хотите только просмотреть/заимствовать что-то, обычно вы увидите «как» ; например Vec::as_slice).

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 должно быть вполне приемлемым (и даже более идиоматическим).

@KuraiAkoraito Да. Но поскольку MutexGuard<T> реализует DerefMut<Target = T>, вы можете получить от него &mut Vec, а затем поменять его на пустой.

Aleksander Krauze 07.08.2024 12:09

Спасибо за ответ, просто чтобы уточнить, есть ли у меня 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(); правильно?

Kurai Akoraito 07.08.2024 12:14

@AleksanderKrauze, возможно, ты захочешь упомянуть Vec::drain?

Ivan C 07.08.2024 12:27

@IvanC хороший улов. Я добавлю это к своему ответу.

Aleksander Krauze 07.08.2024 12:47

Обратите внимание, что drain, скорее всего, будет иметь лучшую производительность, чем take + into_iter, если вы повторно используете Vec. Это потому, что take + into_iter освободит память, а это означает, что вектору нужно будет перераспределить при добавлении новых элементов, тогда как drain позволяет Vec повторно использовать ту же память. (Как всегда, если производительность имеет значение, проверьте тест, чтобы убедиться)

Jmb 07.08.2024 17:21

Другие вопросы по теме