В чем разница между iter и into_iter?

176

Я делаю учебник Rust by Example, который имеет этот фрагмент кода:

// Vec example
let vec1 = vec![1, 2, 3];
let vec2 = vec![4, 5, 6];

// `iter()` for vecs yields `&i32`. Destructure to `i32`.
println!("2 in vec1: {}", vec1.iter()     .any(|&x| x == 2));
// `into_iter()` for vecs yields `i32`. No destructuring required.
println!("2 in vec2: {}", vec2.into_iter().any(| x| x == 2));

// Array example
let array1 = [1, 2, 3];
let array2 = [4, 5, 6];

// `iter()` for arrays yields `&i32`.
println!("2 in array1: {}", array1.iter()     .any(|&x| x == 2));
// `into_iter()` for arrays unusually yields `&i32`.
println!("2 in array2: {}", array2.into_iter().any(|&x| x == 2));

Я полностью сбит с толку - для Vecитератора, возвращенного из iterссылок на урожайность, и итератора, возвращенного из into_iterзначений урожайности, но для массива эти итераторы идентичны?

Каков вариант использования / API для этих двух методов?

vitiral
источник

Ответы:

147

TL; DR:

  • Итератора , возвращенное into_iterможет давать какой - либо из T, &Tили &mut T, в зависимости от контекста.
  • Итератор, возвращаемый функцией, iterбудет выдан &Tпо соглашению.
  • Итератор, возвращаемый функцией, iter_mutбудет выдан &mut Tпо соглашению.

Первый вопрос: «Что есть into_iter

into_iterпроисходит от IntoIteratorчерты :

pub trait IntoIterator 
where
    <Self::IntoIter as Iterator>::Item == Self::Item, 
{
    type Item;
    type IntoIter: Iterator;
    fn into_iter(self) -> Self::IntoIter;
}

Вы реализуете эту особенность, когда хотите указать, как конкретный тип должен быть преобразован в итератор. В частности, если тип реализует IntoIteratorего, он может использоваться в forцикле.

Например, Vecреализует IntoIterator... трижды!

impl<T> IntoIterator for Vec<T>
impl<'a, T> IntoIterator for &'a Vec<T>
impl<'a, T> IntoIterator for &'a mut Vec<T>

Каждый вариант немного отличается.

Этот использует Vecи его итератор выдает значения ( Tнапрямую):

impl<T> IntoIterator for Vec<T> {
    type Item = T;
    type IntoIter = IntoIter<T>;

    fn into_iter(mut self) -> IntoIter<T> { /* ... */ }
}

Два других берут вектор по ссылке (не обманывайте себя сигнатурой, into_iter(self)потому selfчто в обоих случаях это ссылка), и их итераторы будут создавать ссылки на элементы внутри Vec.

Это дает неизменные ссылки :

impl<'a, T> IntoIterator for &'a Vec<T> {
    type Item = &'a T;
    type IntoIter = slice::Iter<'a, T>;

    fn into_iter(self) -> slice::Iter<'a, T> { /* ... */ }
}

В то время как этот дает изменяемые ссылки :

impl<'a, T> IntoIterator for &'a mut Vec<T> {
    type Item = &'a mut T;
    type IntoIter = slice::IterMut<'a, T>;

    fn into_iter(self) -> slice::IterMut<'a, T> { /* ... */ }
}

Так:

В чем разница между iterи into_iter?

into_iterявляется общим методом для получения итератора, независимо от того, выдает ли этот итератор значения, неизменяемые или изменяемые ссылки, зависит от контекста и иногда может вызывать удивление.

iterи iter_mutявляются специальными методами. Поэтому их возвращаемый тип не зависит от контекста и обычно будет итераторами, дающими неизменяемые и изменяемые ссылки соответственно.

Автор поста Rust by Example иллюстрирует удивление, into_iterвызванное зависимостью от контекста (т. Е. Типа), который вызывается, а также усугубляет проблему, используя тот факт, что:

  1. IntoIteratorне реализовано [T; N], только для &[T; N]и&mut [T; N]
  2. Когда метод не реализован для значения, он автоматически ищет ссылки на это значение

что очень удивительно, into_iterпоскольку все типы (кроме [T; N]) реализуют его для всех трех вариантов (значение и ссылки). Массив не может реализовать итератор, который выдает значения, потому что он не может «сжаться», чтобы отказаться от своих элементов.

Относительно того, почему массивы реализуются IntoIterator(таким удивительным образом): это позволяет делать итерации по ссылкам на них в forциклах.

Матье М.
источник
14
Я нашел этот блог пост полезным: hermanradtke.com/2015/06/22/...
Poy
> выдает ли этот итератор значения, неизменяемые или изменяемые ссылки в зависимости от контекста. Что это значит и как с этим бороться? Например, как заставить iter_mut выдавать изменяемые значения?
Дэн М.
@DanM .: (1) Это означает, что into_iterвыбирает реализацию на основе того, является ли получатель значением, ссылкой или изменяемой ссылкой. (2) В Rust нет изменяемых значений, точнее, любое значение является изменяемым, поскольку у вас есть право собственности.
Матье М.
@ MatthieuM.hm, это не так в моих тестах. Я реализовал IntoIter для &'a MyStructи &mut 'a MyStructи первый один всегда был выбран , если присутствует , даже если я позвоню into_iter().for_each()по mutзначению с &mutаргументами в лямбда.
Дэн М.
1
@ Ixx: Спасибо, это очень полезно. Я решил поставить TL; DR в верхней части вопроса, чтобы не спрятать ответ в середине, что вы думаете?
Матье М.
78

Я (новичок в Rust) пришел сюда из Google в поисках простого ответа, который не был предоставлен другими ответами. Вот этот простой ответ:

  • iter() перебирает предметы по ссылке
  • into_iter() перебирает предметы, перемещая их в новую область видимости
  • iter_mut() перебирает элементы, давая изменчивую ссылку на каждый элемент

Так for x in my_vec { ... }что по сути эквивалентно my_vec.into_iter().for_each(|x| ... )- оба moveэлемента my_vecвходят в ...сферу.

Если вам просто нужно «посмотреть» на данные, используйте iter, если вам нужно отредактировать / изменить их, используйте iter_mut, и если вам нужно дать новому владельцу, используйте into_iter.

Это было полезно: http://hermanradtke.com/2015/06/22/effectively-using-iterators-in-rust.html

Сделать это вики-сообществом, чтобы, надеюсь, профессионал Rust мог редактировать этот ответ, если я допустил какие-либо ошибки.

Джо
источник
7
Спасибо ... Трудно увидеть, как принятый ответ четко формулирует различие между iterи into_iter.
ммв
Это именно то, что я искал!
Cyrusmith
6

.into_iter()не реализован для самого массива, но только &[]. Для сравнения:

impl<'a, T> IntoIterator for &'a [T]
    type Item = &'a T

с участием

impl<T> IntoIterator for Vec<T>
    type Item = T

Так IntoIteratorкак определяется только на &[T], сам срез не может быть отброшен так же, как Vecпри использовании значений. (значения не могут быть перемещены)

Теперь, почему это так, это другие вопросы, и я хотел бы узнать сам. Предположение: массив - это сами данные, срез - это только просмотр. На практике вы не можете переместить массив как значение в другую функцию, просто передайте его представление, поэтому вы не сможете использовать его и там.

viraptor
источник
IntoIteratorтакже реализован для &'a mut [T], так что он может перемещать объекты из массива. Я думаю, что это связано с тем фактом, что возвращаемая структура IntoIter<T>не имеет аргумента времени жизни, а Iter<'a, T>первый не может содержать фрагмент.
Родриго
mutозначает, что вы можете изменить значения, а не вывести их.
viraptor
@rodrigo let mut a = ["abc".to_string()]; a.into_iter().map(|x| { *x });=> «ошибка: невозможно
удалить
Да, я думаю, что вы правы, и значения не могут быть перемещены из массива. Тем не менее, я все еще думаю, что должна быть возможность реализовать некую ArrayIntoIterструктуру, использующую небезопасный Rust, как часть библиотеки ... Может быть, это того не стоит, так как в Vecлюбом случае вы должны использовать его в этих случаях.
Родриго
поэтому я не понимаю ... причина в том, что array.into_iterвозвращается &T- потому что он делает магию, чтобы автоматически преобразовать его в &array.into_iter- и если так, я не понимаю, что это имеет отношение к движущимся или не движущимся значениям. Или, как сказал @rodrigo, вы получаете ссылку просто потому, что (по какой-то причине) вы не можете перемещать значения из массивов ? Все еще очень смущен.
vitiral
2

Я думаю, есть кое-что, чтобы уточнить немного больше. Типы коллекций, такие как Vec<T>и VecDeque<T>, имеют into_iterметод, который возвращает, Tпотому что они реализуют IntoIterator<Item=T>. Нет ничего, что могло бы помешать нам создать тип, Foo<T>если он повторяется, это даст не Tдругой тип U. То есть Foo<T>реализует IntoIterator<Item=U>.

На самом деле, есть несколько примеров std: &Path реализует IntoIterator<Item=&OsStr> и &UnixListener реализует IntoIterator<Item=Result<UnixStream>> .


Разница между into_iterиiter

Вернемся к исходному вопросу о разнице между into_iterи iter. Подобно тому, что указывали другие, разница в том, что into_iterэто обязательный метод, IntoIteratorкоторый может давать любой тип, указанный в IntoIterator::Item. Как правило, если тип реализует IntoIterator<Item=I>, по соглашению он также имеет два специальных метода: iterи iter_mutкоторые дают &Iи &mut I, соответственно.

Это означает, что мы можем создать функцию, которая получает тип, у которого есть into_iterметод (т. Е. Он является итеративным), используя границу признака:

fn process_iterable<I: IntoIterator>(iterable: I) {
    for item in iterable {
        // ...
    }
}

Однако мы не можем * использовать признак, связанный с требованием, чтобы у типа был iterметод или iter_mutметод, потому что это просто соглашения. Можно сказать, что into_iterэто более широко применимо, чем iterили iter_mut.

Альтернативы iterиiter_mut

Еще один интересный момент для наблюдения - это iterне единственный способ получить итератор, который дает результат &T. По соглашению (снова), типы коллекций , SomeCollection<T>в stdкоторых есть iterметод также их неизменные ссылочные типы &SomeCollection<T>реализации IntoIterator<Item=&T>. Например, &Vec<T> реализует IntoIterator<Item=&T> , что позволяет нам перебирать &Vec<T>:

let v = vec![1, 2];

// Below is equivalent to: `for item in v.iter() {`
for item in &v {
    println!("{}", item);
}

Если v.iter()эквивалентно тому, &vчто оба реализуют IntoIterator<Item=&T>, почему тогда Rust предоставляет оба? Это для эргономики. В forциклах это немного более сжато, &vчем v.iter(); но в других случаях v.iter()это намного понятнее, чем (&v).into_iter():

let v = vec![1, 2];

let a: Vec<i32> = v.iter().map(|x| x * x).collect();
// Although above and below are equivalent, above is a lot clearer than below.
let b: Vec<i32> = (&v).into_iter().map(|x| x * x).collect();

Аналогично, в forциклах v.iter_mut()можно заменить на &mut v:

let mut v = vec![1, 2];

// Below is equivalent to: `for item in v.iter_mut() {`
for item in &mut v {
    *item *= 2;
}

Когда предоставлять (реализовывать) into_iterи iterметоды для типа

Если у типа есть только один «путь» для итерации, мы должны реализовать оба. Однако, если есть два или более способов, по которым он может повторяться, мы должны вместо этого предоставить специальный метод для каждого способа.

Например, не Stringпредоставляет ни то, into_iterни другое, iterпотому что существует два способа итерировать его: итерировать его представление в байтах или итерировать его представление в символах. Вместо этого он предоставляет два метода: bytesдля итерации байтов и charsдля итерации символов в качестве альтернативы iterметоду.


* Ну, технически мы можем сделать это, создав черту. Но тогда нам нужно implэту черту для каждого типа, который мы хотим использовать. Между тем многие типы stdуже реализованы IntoIterator.

Даниил
источник