Je pense qu'il y a quelque chose à clarifier un peu plus. Les types de collection, tels que Vec<T>
et VecDeque<T>
, ont une into_iter
méthode qui produit T
parce qu'ils implémentent IntoIterator<Item=T>
. Rien ne nous empêche de créer un type Foo<T>
si celui-ci est itéré, il ne donnera pas T
mais un autre type U
. Autrement dit, des Foo<T>
outils IntoIterator<Item=U>
.
En fait, il existe quelques exemples dans std
: les &Path
outils IntoIterator<Item=&OsStr>
et les &UnixListener
outils IntoIterator<Item=Result<UnixStream>>
.
La différence entre into_iter
etiter
Revenons à la question initiale sur la différence entre into_iter
et iter
. Comme d'autres l'ont souligné, la différence est queinto_iter
méthode requise IntoIterator
peut donner n'importe quel type spécifié dans IntoIterator::Item
. En règle générale, si un type implémente IntoIterator<Item=I>
, par convention, il a également deux méthodes ad hoc: iter
et iter_mut
qui donnent &I
et &mut I
, respectivement.
Ce que cela implique, c'est que nous pouvons créer une fonction qui reçoit un type qui a une into_iter
méthode (c'est-à-dire qu'il s'agit d'un itérable) en utilisant un trait lié:
fn process_iterable<I: IntoIterator>(iterable: I) {
for item in iterable {
// ...
}
}
Cependant, nous ne pouvons pas * utiliser un trait lié pour exiger qu'un type ait une iter
méthode ou une iter_mut
méthode, car ce ne sont que des conventions. Nous pouvons dire que into_iter
c'est plus largement utilisable que iter
ou iter_mut
.
Alternatives à iter
etiter_mut
Un autre intéressant à observer est que ce iter
n'est pas la seule façon d'obtenir un itérateur qui cède &T
. Par convention (encore une fois), les types de collection SomeCollection<T>
dans std
lesquels ont la iter
méthode ont également leurs types de référence immuables &SomeCollection<T>
implémentés IntoIterator<Item=&T>
. Par exemple,&Vec<T>
implements IntoIterator<Item=&T>
, donc cela nous permet d'itérer sur &Vec<T>
:
let v = vec![1, 2];
// Below is equivalent to: `for item in v.iter() {`
for item in &v {
println!("{}", item);
}
Si cela v.iter()
équivaut à &v
ces deux implémentations IntoIterator<Item=&T>
, pourquoi alors Rust fournit-il les deux? C'est pour l'ergonomie. Dansfor
boucles, c'est un peu plus concis à utiliser &v
que v.iter()
; mais dans d'autres cas, v.iter()
c'est beaucoup plus clair que (&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();
De même, dans for
boucles, v.iter_mut()
peut être remplacé par &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;
}
Quand fournir (implémenter) into_iter
et iter
méthodes pour un type
Si le type n'a qu'une seule «manière» d'être itérée, nous devons implémenter les deux. Cependant, s'il y a deux façons ou plus de l'itérer, nous devrions plutôt fournir une méthode ad hoc pour chaque façon.
Par exemple, String
ne fournit ni into_iter
ni iter
car il existe deux façons de l'itérer: pour itérer sa représentation en octets ou pour itérer sa représentation en caractères. Au lieu de cela, il fournit deux méthodes: bytes
pour itérer les octets et chars
pour itérer les caractères, comme alternatives à la iter
méthode.
* Eh bien, techniquement, nous pouvons le faire en créant un trait. Mais ensuite, nous avons besoin de impl
ce trait pour chaque type que nous voulons utiliser. Pendant ce temps, de nombreux types sont std
déjà mis en œuvre IntoIterator
.