Jaka jest różnica między iter i into_iter?


175

Robię samouczek dotyczący Rust by Example, który zawiera ten fragment kodu:

// 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));

Jestem całkowicie zdezorientowany - dla Veciteratora zwróconego z iterzwraca odniesienia i iteratora zwróconego z into_iterwartości zwracanych, ale dla tablicy te iteratory są identyczne?

Jaki jest przypadek użycia / API dla tych dwóch metod?

Odpowiedzi:


147

TL; DR:

  • Iterator zwrócony przez into_itermogą dawać każdy z T, &Talbo &mut T, w zależności od kontekstu.
  • Iterator zwrócony przez iterzwróci &Tzgodnie z konwencją.
  • Iterator zwrócony przez iter_mutzwróci &mut Tzgodnie z konwencją.

Pierwsze pytanie brzmi: „Co to jest into_iter?”

into_iterpochodzi z IntoIteratorcechy :

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

Tę cechę implementujesz, gdy chcesz określić, w jaki sposób dany typ ma zostać przekształcony w iterator. Przede wszystkim, jeśli typ implementuje IntoIterator, może być używany w forpętli.

Na przykład Vecwdraża IntoIterator... trzykrotnie!

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

Każdy wariant jest nieco inny.

Ten zużywa the, Veca jego iterator zwraca wartości ( Tbezpośrednio):

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

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

Pozostałe dwa pobierają wektor przez odniesienie (nie dajcie się zwieść podpisowi, into_iter(self)ponieważ selfjest to odniesienie w obu przypadkach), a ich iteratory wytworzą odniesienia do elementów wewnątrz Vec.

Ten daje niezmienne odwołania :

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> { /* ... */ }
}

Chociaż ten daje zmienne odniesienia :

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> { /* ... */ }
}

Więc:

Jaka jest różnica między iteri into_iter?

into_iterjest ogólną metodą uzyskiwania iteratora, niezależnie od tego, czy ten iterator zwraca wartości, niezmienne odwołania, czy zmienne odwołania, zależy od kontekstu i czasami może być zaskakujące.

iteri iter_mutsą metodami ad hoc. Ich typ zwracany jest zatem niezależny od kontekstu i zwykle będą iteratorami dającymi odpowiednio niezmienne i zmienne odwołania.

Autor postu Rust by Example ilustruje zaskoczenie wynikające z zależności od kontekstu (tj. Typu), od którego into_iterjest wywoływany, a także potęguje problem, wykorzystując fakt, że:

  1. IntoIteratornie jest zaimplementowana dla [T; N], tylko dla &[T; N]i&mut [T; N]
  2. Gdy metoda nie jest zaimplementowana dla wartości, zamiast tego jest automatycznie przeszukiwana pod kątem odniesień do tej wartości

co jest bardzo zaskakujące, into_iterponieważ wszystkie typy (z wyjątkiem [T; N]) implementują go dla wszystkich 3 odmian (wartość i odwołania). Tablica nie może zaimplementować iteratora, który zwraca wartości, ponieważ nie może „zmniejszyć”, aby zrezygnować ze swoich elementów.

A dlaczego tablice implementują się IntoIterator(w tak zaskakujący sposób): ma to umożliwić iterację po odwołaniach do nich w forpętlach.


14
Uważam, że ten post na blogu był pomocny: hermanradtke.com/2015/06/22/...
poy

> czy ten iterator zwraca wartości, niezmienne odwołania czy zmienne odwołania, zależy od kontekstu. Co to oznacza i jak sobie z tym radzić? Jak można na przykład zmusić iter_mut do dostarczania zmiennych wartości?
Dan M.

@DanM .: (1) Oznacza to, że into_iterwybiera implementację na podstawie tego, czy odbiornik jest wartością, referencją czy zmienną referencją. (2) W Rust nie ma zmiennych wartości, a raczej każda wartość jest zmienna, ponieważ masz prawo własności.
Matthieu M.

@ MatthieuM.hm, w moich testach nie wydaje się, aby tak było. I zostały wdrożone IntoIter dla &'a MyStructa &mut 'a MyStructi pierwszy został wybrany, jeśli zawsze obecne, nawet jeśli zadzwoniłem into_iter().for_each()na mutwartości z &mutargumentów lambda.
Dan M.

1
@Ixx: Dzięki, to bardzo przydatne. Postanowiłem podać TL; DR u góry pytania, aby uniknąć zakopywania odpowiedzi w środku, co o tym myślisz?
Matthieu M.

78

Ja (nowicjusz w Rust) przyszedłem tutaj z Google, szukając prostej odpowiedzi, której nie dostarczyły inne odpowiedzi. Oto prosta odpowiedź:

  • iter() iteruje po elementach przez odniesienie
  • into_iter() iteruje po elementach, przenosząc je do nowego zakresu
  • iter_mut() iteruje po elementach, podając zmienne odniesienie do każdego elementu

Więc for x in my_vec { ... }jest zasadniczo równoważne my_vec.into_iter().for_each(|x| ... )- obu moveelementom my_vecdo ...zakresu.

Jeśli potrzebujesz tylko „spojrzeć” na dane, użyj iter, jeśli chcesz je edytować / zmutować, użyj iter_mut, a jeśli chcesz nadać im nowego właściciela, użyj into_iter.

To było pomocne: http://hermanradtke.com/2015/06/22/effectively-using-iterators-in-rust.html

Uczynienie z tego wiki społeczności, aby miejmy nadzieję, że profesjonalista Rust mógł edytować tę odpowiedź, jeśli popełniłem jakieś błędy.


7
Dziękuję ... Trudno zobaczyć, jak przyjęta odpowiedź wyraża rozróżnienie między itera into_iter.
mmw

Właśnie tego szukałem!
Cyrusmith

6

.into_iter()nie jest zaimplementowana dla samej tablicy, ale tylko &[]. Porównać:

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

z

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

Ponieważ IntoIteratorjest zdefiniowane tylko na &[T], sam plaster nie może zostać usunięty w taki sam sposób, jak Vecpodczas używania wartości. (wartości nie mogą zostać przeniesione)

Dlaczego tak jest, to inna kwestia i chciałbym się nauczyć. Spekulowanie: tablica to same dane, wycinek to tylko widok do niej. W praktyce nie możesz przenieść tablicy jako wartości do innej funkcji, po prostu przekazać jej widok, więc nie możesz jej również tam skonsumować.


IntoIteratorjest również zaimplementowany dla &'a mut [T], więc może przenosić obiekty z tablicy. Myślę, że jest to związane z faktem, że struktura return IntoIter<T>nie ma argumentu dożywotniego, podczas gdy Iter<'a, T>ma, więc ta pierwsza nie może utrzymać kawałka.
rodrigo

mutoznacza, że ​​możesz zmieniać wartości, a nie przenosić je.
viraptor

@rodrigo let mut a = ["abc".to_string()]; a.into_iter().map(|x| { *x });=> "błąd: nie można wyprowadzić się z wypożyczonych treści"
viraptor

Tak, myślę, że masz rację i nie można przenosić wartości z tablicy. Jednak nadal uważam, że powinno być możliwe zaimplementowanie pewnego rodzaju ArrayIntoIterstruktury przy użyciu niebezpiecznego Rusta, jako części biblioteki ... Może nie warto, bo i tak powinieneś używać Vecw takich przypadkach.
rodrigo

więc nie rozumiem ... jest to powód, który array.into_iterpowraca &T- ponieważ robi magię, aby automatycznie przekształcić ją w &array.into_iter- a jeśli tak, nie rozumiem, co to ma wspólnego z przenoszeniem wartości lub ich nieporuszaniem. Czy jest tak, jak powiedział @rodrigo, że otrzymujesz odniesienie tylko dlatego, że (z jakiegoś powodu) nie możesz przenieść wartości z tablic ? Wciąż bardzo zdezorientowany.
witiralny

2

Myślę, że jest coś do wyjaśnienia. Typy kolekcji, takie jak Vec<T>i VecDeque<T>, mają into_itermetodę, która daje wynik, Tponieważ implementują IntoIterator<Item=T>. Nie ma nic, co mogłoby nas powstrzymać przed utworzeniem typu, Foo<T>jeśli który zostanie powtórzony, przyniesie nie Ttylko inny typ U. To znaczy Foo<T>narzędzia IntoIterator<Item=U>.

W rzeczywistości jest kilka przykładów w std: &Path narzędzia IntoIterator<Item=&OsStr> i &UnixListener narzędzia IntoIterator<Item=Result<UnixStream>> .


Różnica między into_iteriiter

Wracając do pierwotnego pytania o różnicę między into_itera iter. Podobnie jak inni wskazywali, różnica polega na tym, że into_iterjest to wymagana metoda, IntoIteratorktóra może dać dowolny typ określony w IntoIterator::Item. Zazwyczaj, jeśli typ implementuje IntoIterator<Item=I>, zgodnie z konwencją ma również dwie metody ad-hoc: iteri, iter_mutktóre dają &Ii &mut I, odpowiednio.

Oznacza to, że możemy stworzyć funkcję, która otrzyma typ, który ma into_itermetodę (tj. Jest iterowalny), używając powiązanej cechy:

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

Jednak nie możemy * użyć cechy związanej z wymaganiem, aby typ miał itermetodę lub iter_mutmetodę, ponieważ są to tylko konwencje. Można powiedzieć, że into_iterma szersze zastosowanie niż iterlub iter_mut.

Alternatywy dla iteriiter_mut

Inną ciekawą rzeczą iterjest to, że nie jest to jedyny sposób na uzyskanie iteratora, który daje wyniki &T. Zgodnie z konwencją (ponownie) typy kolekcji, SomeCollection<T>w stdktórych mają itermetodę, również mają &SomeCollection<T>implementację niezmiennych typów referencyjnych IntoIterator<Item=&T>. Na przykład &Vec<T> implementuje IntoIterator<Item=&T> , więc pozwala nam iterować po &Vec<T>:

let v = vec![1, 2];

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

Jeśli v.iter()jest równoważne &vw obu implementacjach IntoIterator<Item=&T>, dlaczego więc Rust zapewnia oba? Chodzi o ergonomię. W forpętlach jest to nieco bardziej zwięzłe w użyciu &vniż v.iter(); ale w innych przypadkach v.iter()jest dużo jaśniejszy niż (&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();

Podobnie w forpętlach v.iter_mut()można zastąpić &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;
}

Kiedy podać (zaimplementować) into_iteri itermetody dla typu

Jeśli typ ma tylko jedną „drogę” do iteracji, powinniśmy zaimplementować obie. Jeśli jednak istnieją dwa lub więcej sposobów, które można powtórzyć, powinniśmy zamiast tego zapewnić metodę ad hoc dla każdej z nich.

Na przykład Stringzapewnia ani, into_iterani, iterponieważ istnieją dwa sposoby na iterację: iteracja jej reprezentacji w bajtach lub iteracja jej reprezentacji w znakach. Zamiast tego udostępnia dwie metody: bytesdo iteracji bajtów i charsdo iteracji znaków, jako alternatywę dla itermetody.


* Cóż, technicznie możemy to zrobić, tworząc cechę. Ale potem potrzebujemy impltej cechy dla każdego typu, którego chcemy użyć. Tymczasem wiele typów jest stdjuż wdrożonych IntoIterator.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.