Was ist der Unterschied zwischen iter und into_iter?

173

Ich mache das Rust by Example- Tutorial mit diesem Code-Snippet:

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

Ich bin völlig verwirrt - für einen VecIterator, der von iterErtragsreferenzen zurückgegeben wird, und für einen Iterator, der von Ertragswerten zurückgegeben wird into_iter, aber für ein Array sind diese Iteratoren identisch?

Was ist der Anwendungsfall / die API für diese beiden Methoden?

vitiral
quelle

Antworten:

146

TL; DR:

  • Der Iterator zurück durch into_iterkann jede Ausbeute T, &Toder &mut T, abhängig von dem Kontext.
  • Der von zurückgegebene Iterator iterergibt gemäß &TKonvention.
  • Der von zurückgegebene Iterator iter_mutergibt gemäß &mut TKonvention.

Die erste Frage lautet: "Was ist into_iter?"

into_iterkommt von der IntoIteratorEigenschaft :

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

Sie implementieren dieses Merkmal, wenn Sie angeben möchten, wie ein bestimmter Typ in einen Iterator konvertiert werden soll. Insbesondere wenn ein Typ implementiert wird IntoIterator, kann er in einer forSchleife verwendet werden.

Zum Beispiel Vecimplementiert IntoIterator... dreimal!

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

Jede Variante ist etwas anders.

Dieser verbraucht den Vecund sein Iterator liefert Werte ( Tdirekt):

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

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

Die anderen beiden nehmen den Vektor als Referenz (lassen Sie sich nicht von der Signatur täuschen, into_iter(self)da selfes sich in beiden Fällen um eine Referenz handelt), und ihre Iteratoren erzeugen Verweise auf die darin enthaltenen Elemente Vec.

Dieser liefert unveränderliche Referenzen :

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

Während dieser veränderliche Referenzen liefert :

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

So:

Was ist der Unterschied zwischen iterund into_iter?

into_iterist eine generische Methode, um einen Iterator zu erhalten. Ob dieser Iterator Werte, unveränderliche Referenzen oder veränderbare Referenzen liefert, ist kontextabhängig und kann manchmal überraschend sein.

iterund iter_mutsind Ad-hoc-Methoden. Ihr Rückgabetyp ist daher unabhängig vom Kontext und wird üblicherweise Iteratoren sein, die unveränderliche Referenzen bzw. veränderbare Referenzen liefern.

Der Autor des Beitrags "Rust by Example" veranschaulicht die Überraschung, die sich aus der Abhängigkeit vom Kontext (dh dem Typ) into_iterergibt, von dem aufgerufen wird, und verschärft das Problem auch, indem er Folgendes verwendet:

  1. IntoIteratorist nicht implementiert für [T; N], nur für &[T; N]und&mut [T; N]
  2. Wenn eine Methode für einen Wert nicht implementiert ist , wird stattdessen automatisch nach Verweisen auf diesen Wert gesucht

Dies ist sehr überraschend, into_iterda alle Typen (außer [T; N]) es für alle 3 Variationen (Wert und Referenzen) implementieren. Es ist dem Array nicht möglich, einen Iterator zu implementieren, der Werte liefert, da es nicht "schrumpfen" kann, um seine Elemente aufzugeben.

Warum Arrays IntoIterator(auf so überraschende Weise) implementiert werden : Sie sollen es ermöglichen, Verweise auf sie in forSchleifen zu durchlaufen .

Matthieu M.
quelle
14
Ich fand diesen Blog-Beitrag hilfreich: hermanradtke.com/2015/06/22/…
poy
> ob dieser Iterator Werte, unveränderliche Referenzen oder veränderbare Referenzen liefert, ist kontextabhängig Was bedeutet das und wie geht man damit um? Wie würde man iter_mut zum Beispiel zwingen, veränderbare Werte zu liefern?
Dan M.
@DanM.: (1) Dies bedeutet, dass into_itereine Implementierung basierend darauf ausgewählt wird, ob der Empfänger ein Wert, eine Referenz oder eine veränderbare Referenz ist. (2) Es gibt keine veränderlichen Werte in Rust, oder vielmehr ist jeder Wert veränderbar, da Sie Eigentümer sind.
Matthieu M.
@ MatthieuM.hm, das scheint bei meinen Tests nicht der Fall zu sein. Ich habe implementiert IntoIter für &'a MyStructund &mut 'a MyStructund die erste wurde immer gewählt , falls vorhanden , auch wenn ich rief into_iter().for_each()an mutWert mit &mutArgumenten in Lambda.
Dan M.
1
@Ixx: Danke das ist sehr nützlich. Ich habe beschlossen, oben in der Frage eine TL; DR anzugeben, um die Antwort nicht in der Mitte zu vergraben. Was denkst du?
Matthieu M.
77

Ich (ein Rust-Neuling) kam von Google hierher und suchte nach einer einfachen Antwort, die von den anderen Antworten nicht bereitgestellt wurde. Hier ist diese einfache Antwort:

  • iter() iteriert über die Elemente als Referenz
  • into_iter() iteriert über die Elemente und verschiebt sie in den neuen Bereich
  • iter_mut() iteriert über die Elemente und gibt jedem Element einen veränderlichen Verweis

Also for x in my_vec { ... }ist im Wesentlichen gleichbedeutend mit my_vec.into_iter().for_each(|x| ... )- beiden moveElementen my_vecin den ...Geltungsbereich.

Wenn Sie nur die Daten "betrachten" müssen, verwenden Sie iter, wenn Sie sie bearbeiten / mutieren müssen, verwenden Sie iter_mutund wenn Sie ihr einen neuen Eigentümer geben müssen, verwenden Sie into_iter.

Dies war hilfreich: http://hermanradtke.com/2015/06/22/effectively-using-iterators-in-rust.html

Machen Sie dies zu einem Community-Wiki, damit hoffentlich ein Rust-Profi diese Antwort bearbeiten kann, wenn ich Fehler gemacht habe.

Joe
quelle
7
Danke ... Es ist schwer zu sehen, wie die akzeptierte Antwort eine Unterscheidung zwischen iterund artikuliert into_iter.
mmw
Genau das habe ich gesucht!
Cyrusmith
6

.into_iter()wird nicht für ein Array selbst implementiert, sondern nur &[]. Vergleichen Sie:

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

mit

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

Da IntoIteratornur auf definiert ist &[T], kann das Slice selbst nicht auf die gleiche Weise gelöscht werden, wie Vecwenn Sie die Werte verwenden. (Werte können nicht verschoben werden)

Warum das so ist, ist ein anderes Thema, und ich würde es gerne selbst lernen. Spekulieren: Array sind die Daten selbst, Slice ist nur ein Blick hinein. In der Praxis können Sie das Array nicht als Wert in eine andere Funktion verschieben, sondern nur eine Ansicht davon übergeben, sodass Sie es dort auch nicht verwenden können.

Viraptor
quelle
IntoIteratorauch umgesetzt wird &'a mut [T], so dass es könnte die Objekte aus der Anordnung bewegen. Ich denke, dass es mit der Tatsache zusammenhängt, dass die Rückgabestruktur IntoIter<T>währenddessen Iter<'a, T>kein lebenslanges Argument hat, so dass die erstere kein Slice enthalten kann.
Rodrigo
mutbedeutet, dass Sie die Werte ändern können, nicht, dass Sie sie verschieben können.
Viraptor
@rodrigo let mut a = ["abc".to_string()]; a.into_iter().map(|x| { *x });=> "Fehler: kann nicht aus geliehenen Inhalten
herausziehen
Ja, ich denke Sie haben Recht und Werte können nicht aus dem Array verschoben werden. Ich denke jedoch immer noch, dass es möglich sein sollte, eine Art ArrayIntoIterStruktur mit unsicherem Rust als Teil der Bibliothek zu implementieren ... Vielleicht lohnt es sich nicht, wie Sie es Vecsowieso für diese Fälle verwenden sollten.
Rodrigo
Ich verstehe also nicht, dass dies der Grund ist, der array.into_iterzurückkehrt &T- weil es magisch ist, es automatisch umzuwandeln &array.into_iter- und wenn ja, verstehe ich nicht, was das mit sich bewegenden Werten oder nicht bewegten Werten zu tun hat. Oder ist es, wie @rodrigo sagte, dass Sie die Referenz einfach erhalten, weil Sie (aus irgendeinem Grund) keine Werte aus Arrays verschieben können ? Immer noch sehr verwirrt.
Vitiral
2

Ich denke, es gibt noch etwas zu klären. Auflistungstypen wie Vec<T>und VecDeque<T>haben eine into_iterMethode, die sich ergibt, Tweil sie implementiert werden IntoIterator<Item=T>. Nichts hindert uns daran, einen Typ zu erstellen, der, Foo<T>wenn er wiederholt wird, Tnur einen anderen Typ ergibt U. Das heißt, Foo<T>implementiert IntoIterator<Item=U>.

In der Tat gibt es einige Beispiele in std: &Path Geräte IntoIterator<Item=&OsStr> und &UnixListener Geräte IntoIterator<Item=Result<UnixStream>> .


Der Unterschied zwischen into_iterunditer

Zurück zur ursprünglichen Frage zum Unterschied zwischen into_iterund iter. Ähnlich wie andere darauf hingewiesen haben, besteht der Unterschied darin, dass into_iteres sich um eine erforderliche Methode handelt, IntoIteratordie jeden in angegebenen Typ ergeben kann IntoIterator::Item. Typischerweise wird , wenn ein Typ implementiert IntoIterator<Item=I>, durch Konvention es auch zwei Ad-hoc - Methoden hat: iterund iter_mutderen Ausbeute &Iund &mut I, respectively.

Dies impliziert, dass wir eine Funktion erstellen können, die einen Typ mit einer into_iterMethode empfängt (dh eine iterierbare), indem wir ein gebundenes Merkmal verwenden:

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

Wir können jedoch nicht * ein Merkmal verwenden verpflichtet , eine Art zu verlangen, haben iterVerfahren oder iter_mutVerfahren, weil sie nur Konventionen sind. Wir können sagen, dass dies into_iterweiter verbreitet ist als iteroder iter_mut.

Alternativen zu iterunditer_mut

Ein weiteres interessantes Merkmal ist, dass dies iternicht der einzige Weg ist, einen Iterator zu erhalten, der nachgibt &T. Gemäß der Konvention (wieder) werden bei Sammlungstypen, SomeCollection<T>bei stddenen eine iterMethode vorhanden ist, auch unveränderliche Referenztypen &SomeCollection<T>implementiert IntoIterator<Item=&T>. Zum Beispiel &Vec<T> implementiert IntoIterator<Item=&T> , so dass wir wiederholen können &Vec<T>:

let v = vec![1, 2];

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

Wenn v.iter()dies &vin beiden Implementierungen gleichwertig ist IntoIterator<Item=&T>, warum stellt Rust dann beide bereit? Es ist für die Ergonomie. In forSchleifen ist die Verwendung etwas prägnanter &vals v.iter(); aber in anderen Fällen v.iter()ist viel klarer als (&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();

Ebenso kann in forSchleifen v.iter_mut()ersetzt werden durch &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;
}

Wann (implementiert) into_iterund iterMethoden für einen Typ

Wenn der Typ nur einen "Weg" hat, über den iteriert werden kann, sollten wir beide implementieren. Wenn es jedoch zwei oder mehr Möglichkeiten gibt, die wiederholt werden können, sollten wir stattdessen für jede Möglichkeit eine Ad-hoc-Methode bereitstellen.

Zum Beispiel Stringbietet weder into_iternoch, iterweil es zwei Möglichkeiten gibt, es zu iterieren: seine Darstellung in Bytes zu iterieren oder seine Darstellung in Zeichen zu iterieren. Stattdessen werden zwei Methoden bytesbereitgestellt : zum Iterieren der Bytes und charszum Iterieren der Zeichen als Alternative zur iterMethode.


* Nun, technisch können wir es tun, indem wir ein Merkmal erstellen. Aber dann brauchen wir impldieses Merkmal für jeden Typ, den wir verwenden möchten. Mittlerweile sind viele Typen stdbereits implementiert IntoIterator.

Daniel
quelle