Quelle est la différence entre iter et into_iter?

175

Je suis en train de faire le tutoriel Rust by Example qui contient cet extrait de code:

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

Je suis complètement confus - pour un Vecitérateur renvoyé par des iterréférences de rendements et un itérateur retourné par des into_itervaleurs de rendements, mais pour un tableau, ces itérateurs sont identiques?

Quel est le cas d'utilisation / l'API de ces deux méthodes?

vitiral
la source

Réponses:

147

TL; DR:

  • L'itérateur renvoyé par into_iterpeut donner n'importe lequel de T, &Tou &mut T, selon le contexte.
  • L'itérateur retourné par iterdonnera &T, par convention.
  • L'itérateur retourné par iter_mutdonnera &mut T, par convention.

La première question est: "Qu'est-ce que c'est into_iter?"

into_itervient du IntoIteratortrait :

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

Vous implémentez cette caractéristique lorsque vous souhaitez spécifier la manière dont un type particulier doit être converti en itérateur. Plus particulièrement, si un type implémente, IntoIteratoril peut être utilisé dans une forboucle.

Par exemple, Vecimplémente IntoIterator... trois fois!

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

Chaque variante est légèrement différente.

Celui-ci consomme le Vecet son itérateur donne des valeurs ( Tdirectement):

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

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

Les deux autres prennent le vecteur par référence (ne vous laissez pas berner par la signature into_iter(self)car selfc'est une référence dans les deux cas) et leurs itérateurs produiront des références aux éléments à l'intérieur Vec.

Celui-ci donne des références immuables :

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

Alors que celui-ci donne des références mutables :

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

Alors:

Quelle est la différence entre iteret into_iter?

into_iterest une méthode générique pour obtenir un itérateur, que cet itérateur donne des valeurs, des références immuables ou des références mutables dépend du contexte et peut parfois être surprenant.

iteret iter_mutsont des méthodes ad hoc. Leur type de retour est donc indépendant du contexte, et seront classiquement des itérateurs produisant respectivement des références immuables et des références mutables.

L'auteur de l'article Rust by Example illustre la surprise provenant de la dépendance au contexte (c'est-à-dire le type) sur lequel into_iterest appelé, et aggrave également le problème en utilisant le fait que:

  1. IntoIteratorn'est pas implémenté pour [T; N], uniquement pour &[T; N]et&mut [T; N]
  2. Lorsqu'une méthode n'est pas implémentée pour une valeur, elle est automatiquement recherchée pour les références à cette valeur à la place

ce qui est très surprenant into_itercar tous les types (sauf [T; N]) l'implémentent pour les 3 variations (valeur et références). Il n'est pas possible pour le tableau d'implémenter un itérateur qui donne des valeurs car il ne peut pas "rétrécir" pour abandonner ses éléments.

Quant à savoir pourquoi les tableaux implémentent IntoIterator(de manière si surprenante): c'est pour permettre d'itérer des références à eux dans des forboucles.

Matthieu M.
la source
14
J'ai trouvé ce billet utile: hermanradtke.com/2015/06/22/…
poy
> si cet itérateur produit des valeurs, des références immuables ou des références mutables dépend du contexte. Qu'est-ce que cela signifie et comment y faire face? Comment forcer iter_mut à produire des valeurs mutables, par exemple?
Dan M.
@DanM: (1) Cela signifie que into_iterchoisit une implémentation en fonction du fait que le récepteur est une valeur, une référence ou une référence mutable. (2) Il n'y a pas de valeurs mutables dans Rust, ou plutôt, toute valeur est mutable puisque vous en êtes propriétaire.
Matthieu M.
@ MatthieuM.hm, cela ne semble pas être le cas dans mes tests. J'ai mis en IntoIter pour &'a MyStructet &mut 'a MyStructet le premier était toujours choisi si présent même si j'ai appelé into_iter().for_each()sur la mutvaleur avec des &mutarguments dans lambda.
Dan M.
1
@Ixx: Merci, c'est très utile. J'ai décidé de fournir un TL; DR en haut de la question pour éviter d'enterrer la réponse au milieu, qu'en pensez-vous?
Matthieu M.
78

Je (un débutant de Rust) est venu ici de Google à la recherche d'une réponse simple qui n'était pas fournie par les autres réponses. Voici cette réponse simple:

  • iter() itère sur les éléments par référence
  • into_iter() itère sur les éléments, les déplaçant dans la nouvelle portée
  • iter_mut() itère sur les éléments, donnant une référence mutable à chaque élément

Donc, for x in my_vec { ... }c'est essentiellement équivalent à my_vec.into_iter().for_each(|x| ... )- les deux moveéléments de my_vecdans la ...portée.

Si vous avez juste besoin de "regarder" les données, utilisez iter, si vous avez besoin de les modifier / muter, utilisez iter_mut, et si vous avez besoin de lui donner un nouveau propriétaire, utilisez into_iter.

Cela a été utile: http://hermanradtke.com/2015/06/22/effectively-using-iterators-in-rust.html

Faire de ce wiki un wiki communautaire pour qu'un pro de Rust puisse, espérons-le, éditer cette réponse si j'ai commis des erreurs.

Joe
la source
7
Merci ... Il est difficile de voir comment la réponse acceptée articule une distinction entre iteret into_iter.
mmw
C'est exactement ce que je cherchais!
Cyrusmith
6

.into_iter()n'est pas implémenté pour un tableau lui-même, mais uniquement &[]. Comparer:

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

avec

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

Puisque IntoIteratorest défini uniquement sur &[T], la tranche elle-même ne peut pas être supprimée de la même manière que Veclorsque vous utilisez les valeurs. (les valeurs ne peuvent pas être déplacées)

Maintenant, pourquoi c'est le cas est un problème différent, et j'aimerais apprendre moi-même. Spéculer: le tableau est la donnée elle-même, la tranche n'est qu'une vue de celle-ci. En pratique, vous ne pouvez pas déplacer le tableau en tant que valeur dans une autre fonction, il vous suffit de passer une vue de celui-ci, vous ne pouvez donc pas non plus l'utiliser.

viraptor
la source
IntoIteratorest également implémenté pour &'a mut [T], afin qu'il puisse déplacer les objets hors du tableau. Je pense que cela est lié au fait que la structure de retour IntoIter<T>n'a pas d'argument à vie alors qu'elle le Iter<'a, T>fait, donc la première ne peut pas contenir de tranche.
rodrigo
mut signifie que vous pouvez modifier les valeurs, pas que vous pouvez les déplacer.
viraptor
@rodrigo let mut a = ["abc".to_string()]; a.into_iter().map(|x| { *x });=> "erreur: impossible de sortir du contenu emprunté"
viraptor
Ouais, je pense que vous avez raison et que les valeurs ne peuvent pas être déplacées du tableau. Cependant, je pense toujours qu'il devrait être possible d'implémenter une sorte de ArrayIntoIterstructure en utilisant unsafe Rust, dans le cadre de la bibliothèque ... Peut-être que cela ne vaut pas la peine, comme vous devriez Vecde toute façon l' utiliser pour ces cas.
rodrigo
donc je ne comprends pas ... c'est que la raison qui array.into_iterrevient &T- parce qu'il fait de la magie pour le convertir automatiquement en &array.into_iter- et si c'est le cas, je ne comprends pas ce que cela a à voir avec le déplacement des valeurs ou le non déplacement des valeurs. Ou est-ce comme @rodrigo l'a dit, que vous obtenez la référence simplement parce que (pour une raison quelconque) vous ne pouvez pas déplacer des valeurs hors des tableaux ? Toujours très confus.
vitiral
2

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_iterméthode qui produit Tparce 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 Tmais 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_iteretiter

Revenons à la question initiale sur la différence entre into_iteret iter. Comme d'autres l'ont souligné, la différence est queinto_iter méthode requise IntoIteratorpeut 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: iteret iter_mutqui donnent &Iet &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_itermé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 iterméthode ou une iter_mutméthode, car ce ne sont que des conventions. Nous pouvons dire que into_iterc'est plus largement utilisable que iterou iter_mut.

Alternatives à iteretiter_mut

Un autre intéressant à observer est que ce itern'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 stdlesquels ont la itermé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 à &vces 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 &vque 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_iteret itermé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, Stringne fournit ni into_iterni itercar 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: bytespour itérer les octets et charspour itérer les caractères, comme alternatives à la iterméthode.


* Eh bien, techniquement, nous pouvons le faire en créant un trait. Mais ensuite, nous avons besoin de implce trait pour chaque type que nous voulons utiliser. Pendant ce temps, de nombreux types sont stddéjà mis en œuvre IntoIterator.

Daniel
la source