Qual'è la differenza tra iter e into_iter?


175

Sto facendo il tutorial Rust by Example che ha questo frammento di codice:

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

Sono completamente confuso: per un Veciteratore restituito dai iterriferimenti dei rendimenti e l'iteratore restituito dai into_itervalori dei rendimenti, ma per un array questi iteratori sono identici?

Qual è il caso d'uso / API per questi due metodi?

Risposte:


147

TL; DR:

  • L'iteratore restituito da into_iterpuò produrre qualsiasi T, &To &mut T, a seconda del contesto.
  • L'iteratore restituito da itercederà &T, per convenzione.
  • L'iteratore restituito da iter_mutcederà &mut T, per convenzione.

La prima domanda è: "Che cos'è into_iter?"

into_iterderiva dal IntoIteratortratto :

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

Si implementa questo tratto quando si desidera specificare come un particolare tipo deve essere convertito in un iteratore. In particolare, se un tipo lo implementa IntoIterator, può essere utilizzato in un forciclo.

Ad esempio, Vecimplementa IntoIterator... tre volte!

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

Ogni variante è leggermente diversa.

Questo consuma Vece il suo iteratore produce valori ( Tdirettamente):

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

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

Gli altri due prendono il vettore per riferimento (non lasciatevi ingannare dalla firma di into_iter(self)perché selfè un riferimento in entrambi i casi) e i loro iteratori produrranno riferimenti agli elementi all'interno Vec.

Questo fornisce riferimenti immutabili :

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

Mentre questo produce riferimenti mutabili :

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

Così:

Qual è la differenza tra itere into_iter?

into_iterè un metodo generico per ottenere un iteratore, indipendentemente dal fatto che questo iteratore produca valori, riferimenti immutabili o riferimenti mutabili dipende dal contesto e talvolta può essere sorprendente.

itere iter_mutsono metodi ad hoc. Il loro tipo di ritorno è quindi indipendente dal contesto e convenzionalmente saranno iteratori che forniranno rispettivamente riferimenti immutabili e riferimenti mutabili.

L'autore del post Rust by Example illustra la sorpresa derivante dalla dipendenza dal contesto (cioè il tipo) su cui into_iterviene chiamato, e sta anche aggravando il problema usando il fatto che:

  1. IntoIteratornon è implementato per [T; N], solo per &[T; N]e&mut [T; N]
  2. Quando un metodo non è implementato per un valore, viene invece cercato automaticamente i riferimenti a quel valore

il che è molto sorprendente into_iterpoiché tutti i tipi (tranne [T; N]) lo implementano per tutte e 3 le variazioni (valore e riferimenti). Non è possibile per l'array implementare un iteratore che restituisce valori perché non può "ridursi" per rinunciare ai suoi elementi.

Sul motivo per cui le matrici si implementano IntoIterator(in modo così sorprendente): è per rendere possibile iterare i riferimenti ad esse in forloop.


14
Ho trovato questo post del blog utile: hermanradtke.com/2015/06/22/...
POY

> se questo iteratore produce valori, riferimenti immutabili o riferimenti mutabili dipende dal contesto Cosa significa e come gestirlo? Come si potrebbe forzare iter_mut a produrre valori mutabili, per esempio?
Dan M.,

@DanM .: (1) Significa che into_iterseleziona un'implementazione in base al fatto che il destinatario sia un valore, un riferimento o un riferimento modificabile. (2) Non ci sono valori modificabili in Rust, o meglio, qualsiasi valore è mutabile poiché si possiede la proprietà.
Matthieu M.,

@ MatthieuM.hm, questo non sembra essere il caso nei miei test. Ho implementato IntoIter per &'a MyStructe &mut 'a MyStructe il primo è sempre stato scelto se presente anche se ho chiamato into_iter().for_each()il mutvalore con &mutargomenti in lambda.
Dan M.,

1
@Ixx: grazie, è molto utile. Ho deciso di fornire un TL; DR nella parte superiore della domanda per evitare di seppellire la risposta nel mezzo, cosa ne pensi?
Matthieu M.,

78

Io (un novizio di Rust) sono venuto qui da Google alla ricerca di una risposta semplice che non era fornita dalle altre risposte. Ecco quella semplice risposta:

  • iter() scorre gli articoli per riferimento
  • into_iter() scorre gli elementi, spostandoli nel nuovo ambito
  • iter_mut() scorre gli elementi, fornendo un riferimento modificabile a ciascun elemento

Quindi for x in my_vec { ... }è essenzialmente equivalente a my_vec.into_iter().for_each(|x| ... )- entrambi movegli elementi my_vecall'interno ...dell'ambito.

Se hai solo bisogno di "guardare" i dati, usa iter, se hai bisogno di modificarli / mutarli, usa iter_mut, e se hai bisogno di dargli un nuovo proprietario, usa into_iter.

Questo è stato utile: http://hermanradtke.com/2015/06/22/effectively-using-iterators-in-rust.html

Rendendo questo un wiki della community in modo che, eventualmente, un Rust pro possa modificare questa risposta se ho fatto degli errori.


7
Grazie ... È difficile vedere come la risposta accettata articoli una distinzione tra itere into_iter.
mmw

Questo è esattamente quello che stavo cercando!
Cyrusmith,

6

.into_iter()non è implementato per un array stesso, ma solo &[]. Confrontare:

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

con

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

Poiché IntoIteratorè definito solo su &[T], la sezione stessa non può essere rilasciata allo stesso modo di Vecquando si utilizzano i valori. (i valori non possono essere spostati)

Ora, perché è così è un problema diverso, e mi piacerebbe imparare me stesso. Speculazione: l'array è il dato stesso, lo slice è solo una vista al suo interno. In pratica non è possibile spostare l'array come valore in un'altra funzione, è sufficiente passarne una vista, quindi non è possibile utilizzarlo nemmeno lì.


IntoIteratorè anche implementato per &'a mut [T], quindi potrebbe spostare gli oggetti fuori dall'array. Penso che sia correlato al fatto che la struttura di ritorno IntoIter<T>non ha un argomento a vita mentre lo Iter<'a, T>fa, quindi il primo non può contenere una fetta.
Rodrigo,

mutsignifica che puoi cambiare i valori, non che puoi spostarli.
viraptor,

@rodrigo let mut a = ["abc".to_string()]; a.into_iter().map(|x| { *x });=> "errore: impossibile spostarsi dal contenuto preso in prestito"
viraptor il

Sì, penso che tu abbia ragione e che i valori non possano essere spostati dall'array. Tuttavia, penso ancora che dovrebbe essere possibile implementare una sorta di ArrayIntoIterstruct usando Rust non sicuro, come parte della libreria ... Forse non ne vale la pena, come dovresti usare Veccomunque per quei casi.
Rodrigo,

quindi non capisco ... è questo il motivo che array.into_iterritorna &T- perché sta facendo magie per convertirlo automaticamente in &array.into_iter- e in tal caso, non capisco cosa abbia a che fare con lo spostamento di valori o non lo spostamento di valori. O è come ha detto @rodrigo, che si ottiene il riferimento semplicemente perché (per qualche motivo) non è possibile spostare i valori fuori dagli array ? Ancora molto confuso.
vitirale

2

Penso che ci sia qualcosa da chiarire un po 'di più. I tipi di raccolta, come Vec<T>e VecDeque<T>, hanno un into_itermetodo che produce Tperché implementano IntoIterator<Item=T>. Non c'è nulla che ci impedisca di creare un tipo Foo<T>se è ripetuto, non produrrà Taltro tipo U. Cioè, Foo<T>implementa IntoIterator<Item=U>.

In effetti, ci sono alcuni esempi in std: &Path attrezzi IntoIterator<Item=&OsStr> e &UnixListener attrezzi IntoIterator<Item=Result<UnixStream>> .


La differenza tra into_itereiter

Torna alla domanda originale sulla differenza tra into_itere iter. Simile a quello che altri hanno sottolineato, la differenza è che into_iterè un metodo richiesto IntoIteratorche può produrre qualsiasi tipo specificato in IntoIterator::Item. Tipicamente, se un tipo implementa IntoIterator<Item=I>, per convenzione ha anche due metodi ad-hoc: itere iter_mutche producono &Ie &mut I, rispettivamente.

Ciò che implica è che possiamo creare una funzione che riceve un tipo che ha un into_itermetodo (cioè è un iterabile) usando un limite tratto:

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

Tuttavia, non possiamo * usare un tratto associato per richiedere che un tipo abbia un itermetodo o un iter_mutmetodo, perché sono solo convenzioni. Possiamo dire che into_iterè più ampiamente utilizzabile di itero iter_mut.

Alternative a itereiter_mut

Un altro interessante da osservare è che iternon è l'unico modo per ottenere un iteratore che cede &T. Per convenzione (di nuovo), i tipi di raccolta SomeCollection<T>in stdcui hanno itermetodo hanno anche i loro tipi di riferimento immutabili &SomeCollection<T>implementare IntoIterator<Item=&T>. Ad esempio, &Vec<T> implementa IntoIterator<Item=&T> , quindi ci consente di iterare su &Vec<T>:

let v = vec![1, 2];

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

Se v.iter()è equivalente a &vquello in entrambi gli strumenti IntoIterator<Item=&T>, perché Rust fornisce entrambi? È per l'ergonomia. Nei forloop, è un po 'più conciso da usare &vdi v.iter(); ma in altri casi, v.iter()è molto più chiaro di (&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();

Allo stesso modo, in forloop, v.iter_mut()può essere sostituito con &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;
}

Quando fornire (implementare) into_itere itermetodi per un tipo

Se il tipo ha un solo "modo" su cui iterare, dovremmo implementare entrambi. Tuttavia, se ci sono due o più modi in cui può essere ripetuto, dovremmo invece fornire un metodo ad hoc per ciascun modo.

Ad esempio, Stringnon fornisce né into_iteriterperché ci sono due modi per iterarlo: iterare la sua rappresentazione in byte o iterare la sua rappresentazione in caratteri. Fornisce invece due metodi: bytesper iterare i byte e charsper iterare i caratteri, in alternativa al itermetodo.


* Beh, tecnicamente possiamo farlo creando un tratto. Ma allora abbiamo bisogno di implquel tratto per ogni tipo che vogliamo usare. Nel frattempo, molti tipi stdgià implementano IntoIterator.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.