¿Cuál es la diferencia entre iter e into_iter?

174

Estoy haciendo el tutorial Rust by Example que tiene este fragmento de código:

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

Estoy completamente confundido: para un Veciterador devuelto de las iterreferencias de rendimiento y el iterador devuelto de los into_itervalores de rendimiento, pero para una matriz, estos iteradores son idénticos.

¿Cuál es el caso de uso / API para estos dos métodos?

vitiral
fuente

Respuestas:

146

TL; DR:

  • El iterador devuelto por into_iterpuede producir cualquiera de T, &To &mut T, dependiendo del contexto.
  • El iterador devuelto por iterrendirá &T, por convención.
  • El iterador devuelto por iter_mutrendirá &mut T, por convención.

La primera pregunta es: "¿Qué es into_iter?"

into_iterproviene del IntoIteratorrasgo :

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

Implementa este rasgo cuando desea especificar cómo se convertirá un tipo particular en un iterador. En particular, si un tipo lo implementa IntoIterator, puede usarse en un forbucle.

Por ejemplo, Vecimplementa IntoIterator... ¡tres veces!

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

Cada variante es ligeramente diferente.

Este consume el Vecy su iterador produce valores ( Tdirectamente):

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

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

Los otros dos toman el vector por referencia (no se deje engañar por la firma de into_iter(self)porque selfes una referencia en ambos casos) y sus iteradores producirán referencias a los elementos en su interior Vec.

Este produce referencias inmutables :

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

Si bien este produce referencias 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> { /* ... */ }
}

Entonces:

¿Cuál es la diferencia entre itery into_iter?

into_iteres un método genérico para obtener un iterador, si este iterador produce valores, referencias inmutables o referencias mutables depende del contexto y a veces puede ser sorprendente.

itery iter_mutson métodos ad-hoc. Su tipo de retorno es, por lo tanto, independiente del contexto, y convencionalmente serán iteradores que producirán referencias inmutables y referencias mutables, respectivamente.

El autor de la publicación Rust by Example ilustra la sorpresa que viene de la dependencia del contexto (es decir, el tipo) al que into_iterse llama, y ​​también agrava el problema al usar el hecho de que:

  1. IntoIteratorno está implementado para [T; N], solo para &[T; N]y&mut [T; N]
  2. Cuando un método no se implementa para un valor, se busca automáticamente referencias a ese valor

lo cual es muy sorprendente into_iterya que todos los tipos (excepto [T; N]) lo implementan para las 3 variaciones (valor y referencias). No es posible que la matriz implemente un iterador que arroje valores porque no puede "reducir" para renunciar a sus elementos.

En cuanto a por qué se implementan las matrices IntoIterator(de una manera tan sorprendente): es para hacer posible iterar sobre las referencias a ellas en forbucles.

Matthieu M.
fuente
14
Esta publicación del blog me pareció útil: hermanradtke.com/2015/06/22/…
poy
> si este iterador produce valores, referencias inmutables o referencias mutables depende del contexto ¿Qué significa y cómo lidiar con eso? ¿Cómo forzaría a iter_mut a producir valores mutables, por ejemplo?
Dan M.
@DanM .: (1) Significa que into_iterelige una implementación en función de si el receptor es un valor, referencia o referencia mutable. (2) No hay valores mutables en Rust, o más bien, cualquier valor es mutable ya que usted tiene la propiedad.
Matthieu M.
@ MatthieuM.hm, ese no parece ser el caso en mis pruebas. Implementé IntoIter para &'a MyStructy, &mut 'a MyStructy el primero siempre se elegía si estaba presente, incluso si invoqué into_iter().for_each()el mutvalor con &mutargumentos en lambda.
Dan M.
1
@ Ixx: Gracias, eso es muy útil. Decidí proporcionar un TL; DR en la parte superior de la pregunta para evitar enterrar la respuesta en el medio, ¿qué opinas?
Matthieu M.
78

Yo (un novato de Rust) vine de Google buscando una respuesta simple que no fue proporcionada por las otras respuestas. Aquí está esa respuesta simple:

  • iter() itera sobre los elementos por referencia
  • into_iter() itera sobre los elementos, moviéndolos al nuevo alcance
  • iter_mut() itera sobre los elementos, dando una referencia mutable a cada elemento

Así que for x in my_vec { ... }es esencialmente equivalente a my_vec.into_iter().for_each(|x| ... )- tanto movelos elementos de my_vecen el ...ámbito de aplicación.

Si solo necesita "mirar" los datos, use iter, si necesita editarlos / mutarlos, use iter_mut, y si necesita darle un nuevo propietario, use into_iter.

Esto fue útil: http://hermanradtke.com/2015/06/22/effectively-using-iterators-in-rust.html

Hacer de este un wiki de la comunidad para que un profesional de Rust pueda editar esta respuesta si cometí algún error

joe
fuente
77
Gracias ... Es difícil ver cómo la respuesta aceptada articula una distinción entre itery into_iter.
mmw
¡Eso es exactamente lo que estaba buscando!
Cyrusmith
6

.into_iter()no se implementa para una matriz en sí, sino solo &[]. Comparar:

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

con

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

Como IntoIteratorse define solo en &[T], el segmento en sí no se puede descartar de la misma manera que Veccuando usa los valores. (los valores no se pueden mover)

Ahora, por qué ese es el caso es un problema diferente, y me gustaría aprender yo mismo. Especulando: la matriz son los datos en sí, el segmento es solo una vista. En la práctica, no puede mover la matriz como valor a otra función, simplemente pasar una vista de ella, por lo que tampoco puede consumirla allí.

viraptor
fuente
IntoIteratortambién está implementado para &'a mut [T], por lo que podría mover los objetos fuera de la matriz. Creo que está relacionado con el hecho de que la estructura de retorno IntoIter<T>no tiene un argumento de por vida mientras que Iter<'a, T>sí, por lo que el primero no puede contener un segmento.
rodrigo
mutsignifica que puede cambiar los valores, no que puede moverlos.
viraptor
@rodrigo let mut a = ["abc".to_string()]; a.into_iter().map(|x| { *x });=> "error: no se puede salir del contenido prestado"
viraptor
Sí, creo que tienes razón y los valores no se pueden mover de la matriz. Sin embargo, sigo pensando que debería ser posible implementar un tipo de ArrayIntoIterestructura utilizando Rust inseguro, como parte de la biblioteca ... Tal vez no valga la pena, ya que de Vectodos modos debería usarlo para esos casos.
rodrigo
así que no entiendo ... es esa la razón por la que array.into_iterregresa &T, porque está haciendo magia convertirlo automáticamente a &array.into_iter, y si es así, no entiendo qué tiene que ver con valores móviles o no valores móviles. ¿O es como dijo @rodrigo, que obtienes la referencia simplemente porque (por alguna razón) no puedes mover valores fuera de las matrices ? Aún muy confundido.
vitiral
2

Creo que hay algo que aclarar un poco más. Los tipos de colección, como Vec<T>y VecDeque<T>, tienen un into_itermétodo que rinde Tporque se implementan IntoIterator<Item=T>. No hay nada que nos detenga para crear un tipo Foo<T>si se repite, no dará Tsino otro tipo U. Es decir, Foo<T>implementos IntoIterator<Item=U>.

De hecho, hay algunos ejemplos en std: &Path implementos IntoIterator<Item=&OsStr> e &UnixListener implementos IntoIterator<Item=Result<UnixStream>> .


La diferencia entre into_iteryiter

Volviendo a la pregunta original sobre la diferencia entre into_itery iter. Similar a lo que otros han señalado, la diferencia es que into_iteres un método requerido IntoIteratorque puede producir cualquier tipo especificado en IntoIterator::Item. Típicamente, si un tipo se implementa IntoIterator<Item=I>, por convención también tiene dos métodos ad-hoc: itery iter_mutque rinden &Iy &mut I, respectivamente.

Lo que implica es que podemos crear una función que recibe un tipo que tiene un into_itermétodo (es decir, es iterable) mediante el uso de un límite de rasgo:

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

Sin embargo, no podemos * usar un rasgo obligado a requerir que un tipo tenga un itermétodo o iter_mutmétodo, porque son solo convenciones. Podemos decir que into_iteres más ampliamente utilizable que itero iter_mut.

Alternativas a iteryiter_mut

Otro iteraspecto interesante para observar es que no es la única forma de obtener un iterador que rinda &T. Por convención (nuevamente), los tipos de colección SomeCollection<T>en los stdque tienen itermétodo también tienen &SomeCollection<T>implementados sus tipos de referencia inmutables IntoIterator<Item=&T>. Por ejemplo, &Vec<T> implementa IntoIterator<Item=&T> , por lo que nos permite iterar sobre &Vec<T>:

let v = vec![1, 2];

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

Si v.iter()es equivalente a &vque ambos implementos IntoIterator<Item=&T>, ¿por qué Rust proporciona ambos? Es para la ergonomía. En forbucles, es un poco más conciso de usar &vque v.iter(); pero en otros casos, v.iter()es mucho más claro 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();

Del mismo modo, en forbucles, v.iter_mut()se puede reemplazar 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;
}

Cuándo proporcionar (implementar) into_itery itermétodos para un tipo

Si el tipo solo tiene una "forma" para iterar, deberíamos implementar ambas. Sin embargo, si hay dos formas o más de que se puede repetir, deberíamos proporcionar un método ad-hoc para cada forma.

Por ejemplo, Stringno proporciona into_iterni iterporque hay dos formas de iterarlo: iterar su representación en bytes o iterar su representación en caracteres. En cambio, proporciona dos métodos: bytespara iterar los bytes y charspara iterar los caracteres, como alternativas al itermétodo.


* Bueno, técnicamente podemos hacerlo creando un rasgo. Pero entonces necesitamos implese rasgo para cada tipo que queremos usar. Mientras tanto, muchos tipos stdya se implementan IntoIterator.

Daniel
fuente