Яка різниця між iter та into_iter?


175

Я роблю підручник " Іржа за прикладом", який містить цей фрагмент коду:

// 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 для цих двох методів?

Відповіді:


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є спеціальними методами. Тому тип їх повернення не залежить від контексту, і, як правило, це ітератори, що дають незмінні посилання та змінні посилання відповідно.

Автор публікації "Іржа за прикладом" ілюструє сюрприз, що into_iterвиникає через залежність від контексту (тобто типу), про який викликається, а також ускладнює проблему, використовуючи той факт, що:

  1. IntoIteratorне реалізовано [T; N]лише для &[T; N]та&mut [T; N]
  2. Коли метод не реалізований для значення, він автоматично шукає посилання на це значення

що дуже дивно, into_iterоскільки всі типи (крім [T; N]) реалізують його для всіх 3 варіантів (значення та посилання). Масив не може реалізувати ітератор, який дає значення, оскільки він не може "зменшити", щоб відмовитись від своїх елементів.

Щодо того, чому масиви реалізують 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

Я (іржавий новачок) приїхав сюди від 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.
mmw

Це саме те, що я шукав!
Сайрусміт

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при використанні значень. (значення не можна переміщувати)

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


IntoIteratorтакож реалізований для &'a mut [T], щоб він міг переміщувати об'єкти з масиву. Я думаю, що це пов’язано з тим, що структура return IntoIter<T>не має аргументу довічного життя Iter<'a, T>, тому перша не може утримувати фрагмент.
rodrigo

mutозначає, що ви можете змінити значення, а не що ви можете їх перемістити.
viraptor

@rodrigo let mut a = ["abc".to_string()]; a.into_iter().map(|x| { *x });=> "помилка: неможливо перейти із запозиченого вмісту"
viraptor

Так, я думаю, ви праві, і значення не можна переміщувати з масиву. Однак я все-таки думаю, що має бути можливим реалізувати якусь ArrayIntoIterструктуру, використовуючи небезпечну Русту як частину бібліотеки ... Можливо, не варто, як це потрібно використовувати Vecдля тих випадків.
rodrigo

тому я не розумію ... в тому, що причина array.into_iterповертається &T- тому що вона робить магію для автоматичного перетворення її в &array.into_iter- і якщо так, я не розумію, що це має відношення до рухомих значень чи не рухомих значень. Або, як сказав @rodrigo, ви отримуєте посилання просто тому, що (з якоїсь причини) ви не можете переміщувати значення з масивів ? Ще дуже розгублений.
витірал

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()рівнозначно , чому тоді Rust надає обоє? Це для ергономіки. У циклах використовувати трохи більш стисло, ніж ; але в інших випадках набагато зрозуміліше, ніж :&vIntoIterator<Item=&T>for&vv.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.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.