wandres.dev
ITERADORES · la pereza y su poder

IntoIterator: iter, iter_mut, into_iter y cómo for los usa

El trait `IntoIterator` es el puente que el bucle `for` cruza: `for x in c` se desazucara en `c.into_iter()`. Una colección ofrece tres iteradores —`iter` presta `&T`, `iter_mut` presta `&mut T`, `into_iter` mueve `T`—, y elegir entre ellos es elegir el modo de propiedad. Por qué `for x in v`, `for x in &v` y `for x in &mut v` recorren lo mismo de tres maneras.

⏱ 19 min

Llevas todo el nivel diciendo “el for habla con next”. Ahora se cierra el círculo. Entre una colección y un iterador media un trait, IntoIterator, y el bucle for no es más que azúcar sobre él: for x in coleccion se convierte en pedir coleccion.into_iter() y tirar de next hasta el None. Pero hay una elección escondida en ese puente. Una misma colección ofrece tres maneras de convertirse en iterador —prestar, prestar para mutar o entregarse por completo—, y cuál eliges lo decides con algo tan pequeño como un &. Iterar, resulta, no es una operación: son tres, y las tres son las tres formas de propiedad de Rust proyectadas sobre el recorrido.

🎯 Al terminar esta lección sabrás
  • Leer el trait IntoIterator y ver cómo for se desazucara en into_iter.
  • Distinguir iter, iter_mut e into_iter por el tipo de Item que producen.
  • Elegir entre prestar, mutar y consumir al recorrer una colección.
  • Saber qué hace for x in v, for x in &v y for x in &mut v.

IntoIterator: el puente que for cruza

IntoIterator es el trait de “esto se puede convertir en un iterador”. Su método into_iter toma self y devuelve un iterador:

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

Y el bucle for es exactamente esto, escrito por ti con menos ruido. Esta línea:

for x in coleccion {
    usar(x);
}

es azúcar sintáctico para, en esencia:

let mut it = coleccion.into_iter();
while let Some(x) = it.next() {
    usar(x);
}

El for no sabe nada de Vec, de HashMap ni de rangos: solo sabe pedir into_iter y llamar a next. Cualquier tipo que implemente IntoIterator es recorrible con for, y por eso el mismo bucle sirve para toda colección de la biblioteca y para las tuyas.

iter, iter_mut, into_iter: prestar, mutar, consumir

Aquí está el corazón del tema. Las colecciones ofrecen tres formas de iterar, que se diferencian solo en el tipo de Item que producen —y ese tipo lo cambia todo—:

let v = vec![1, 2, 3];

v.iter();       // Item = &i32     : presta cada elemento en solo lectura
v.iter_mut();   // Item = &mut i32 : presta cada elemento para modificarlo
v.into_iter();  // Item = i32      : entrega cada elemento moviendolo
  • iter produce referencias compartidas &T: lees, la colección sigue viva e intacta después.
  • iter_mut produce referencias mutables &mut T: modificas en el sitio, la colección sigue siendo tuya.
  • into_iter produce valores T: mueve cada elemento fuera de la colección, que queda consumida.
let mut v = vec![1, 2, 3];
for x in v.iter_mut() {
    *x *= 10;                 // x es &mut i32: modifico el elemento real
}
// v == [10, 20, 30], sigue siendo mia

let total: i32 = v.iter().sum();   // la leo sin consumirla

for sobre valor, referencia o referencia mutable

No hace falta llamar a los tres métodos a mano: for los selecciona según cómo le des la colección, porque IntoIterator está implementado para Vec<T>, para &Vec<T> y para &mut Vec<T>, cada uno con un Item distinto:

// Cada linea ilustra una forma distinta, por separado:
for s in &v { }        // s: &String      presta, v sobrevive
for s in &mut v { }    // s: &mut String  muta en el sitio, v debe ser mut
for s in v { }         // s: String       mueve, v se consume aqui

La regla es mecánica y hermosa: &v desemboca en iter, &mut v en iter_mut, y v a secas en into_iter. Ese & no es decorativo: es la palanca con la que eliges entre observar, modificar o consumir. Y el compilador lo respeta hasta el final: tras for s in v, usar v es un error de “valor movido”, porque el bucle se llevó la colección entera.

let v = vec![1, 2, 3];
for x in v { }              // into_iter: v se consume
// println!("{v:?}");       // ERROR: v fue movida por el for

Un matiz de la edición: arrays por valor

Desde la edición 2021 —y por tanto asentado en la 2024 que usas—, un array también implementa IntoIterator por valor: for x in [1, 2, 3] entrega i32, no &i32. Si quieres referencias, itera sobre &[1, 2, 3]. Es la misma regla de siempre —el & elige el modo— aplicada por fin también a los arrays.

for x in [1, 2, 3] { }      // x: i32   (por valor, desde la edicion 2021)
for x in &[1, 2, 3] { }     // x: &i32  (prestado)
👁️

iter → &T

Presta cada elemento en solo lectura. La colección sobrevive intacta. Lo eliges con for x in &c.

✏️

iter_mut → &mut T

Presta cada elemento para modificarlo en el sitio. La colección sigue siendo tuya. Lo eliges con for x in &mut c.

📦

into_iter → T

Mueve cada elemento fuera. La colección se consume y ya no se usa. Lo eliges con for x in c.

flowchart TB
bucle[for x in expr] --> ii[Invoca into_iter sobre expr]
ii --> tira[while let Some x igual it next]
tira --> cuerpo[Ejecuta el cuerpo del bucle]
cuerpo --> tira
presta[Iterar sobre una referencia] --> t1[Item es referencia compartida solo lee]
muta[Iterar sobre referencia mutable] --> t2[Item es referencia mutable modifica en sitio]
mueve[Iterar sobre el valor] --> t3[Item es el valor consume la coleccion]
style ii fill:#89b4fa,color:#11111b
style t1 fill:#a6e3a1,color:#11111b
style t2 fill:#f9e2af,color:#11111b
style t3 fill:#f38ba8,color:#11111b
Iterar es la propiedad proyectada sobre el recorrido

IntoIterator parece un mero detalle de fontanería —el trait que hace funcionar el for—, pero encierra una de las síntesis más elegantes de Rust. Primero, unifica: el bucle for no conoce ninguna colección en particular, solo el trait, de modo que un único constructo del lenguaje recorre vectores, mapas, conjuntos, rangos, canales y cualquier tipo que alguien defina mañana, sin que el for cambie ni el compilador sepa nada de antemano. Esa es la misma lección de los genéricos y los traits llevada a la sintaxis: el lenguaje se mantiene pequeño y abierto porque delega la capacidad en un contrato. Pero la síntesis honda está en los tres modos. Una colección ofrece iter, iter_mut e into_iter, y no se distinguen por lo que hacen —recorrer— sino por el tipo de su Item: &T, &mut T, T. Y esos tres tipos son, exactamente, los tres modos de acceso sobre los que se construye todo el lenguaje: el préstamo compartido, el préstamo mutable y la posesión. El sistema de propiedad de Rust, que gobierna cada línea que escribes, reaparece aquí intacto, proyectado sobre el acto de recorrer: iterar prestando, iterar mutando, iterar consumiendo. Por eso el & de for x in &v carga tanto significado en tan poco: no adorna, decide si el bucle observará, modificará o devorará la colección, y el borrow checker vela por esa decisión como por cualquier otra —tras un for x in v la colección está movida, y el compilador te lo recuerda—. Y en el otro extremo del nivel, collect con FromIterator es el reflejo de este trait: into_iter deshace una estructura en un flujo, collect cuaja un flujo en una estructura. Entre los dos, todo el procesamiento de secuencias de Rust es un viaje de ida y vuelta entre las colecciones y los flujos perezosos, con la propiedad marcando el camino en cada paso.

⚠️
Tras consumir con for x in v, la colección ya no existe

for x in v mueve v dentro del bucle: al terminar, la colección ha sido consumida y usarla de nuevo es un error de compilación. Si necesitas la colección después del bucle, itera sobre &v (o &mut v si vas a modificarla) en lugar de sobre v. Es el mismo criterio que aplicas al llamar a una función que toma self frente a una que toma &self: el for no es una excepción a las reglas de propiedad, es una de sus aplicaciones.

⚔️ Elige el modo de recorrer
  1. Con let mut v = vec![1, 2, 3];, usa for x in &mut v { *x += 100; } y luego imprime v. Explica por qué v debe ser mut y qué tipo tiene x.
  2. Recorre for x in &v sumando en una variable externa y comprueba que después puedes seguir usando v. ¿Qué tipo tiene x?
  3. Escribe for s in v sobre un Vec<String> y luego intenta usar v; lee el error de “valor movido” e identifica qué método invocó el for.
  4. Desazucara a mano for x in 1..=3 en su forma con into_iter y while let Some, y comprueba que hace lo mismo.
  5. Itera for x in [10, 20, 30] y luego for x in &[10, 20, 30]; contrasta el tipo de x en cada caso y relaciónalo con el cambio de la edición 2021.