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.
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.
- Leer el trait
IntoIteratory ver cómoforse desazucara eninto_iter. - Distinguir
iter,iter_muteinto_iterpor el tipo deItemque producen. - Elegir entre prestar, mutar y consumir al recorrer una colección.
- Saber qué hace
for x in v,for x in &vyfor 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
iterproduce referencias compartidas&T: lees, la colección sigue viva e intacta después.iter_mutproduce referencias mutables&mut T: modificas en el sitio, la colección sigue siendo tuya.into_iterproduce valoresT: 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
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.
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.
- Con
let mut v = vec![1, 2, 3];, usafor x in &mut v { *x += 100; }y luego imprimev. Explica por quévdebe sermuty qué tipo tienex. - Recorre
for x in &vsumando en una variable externa y comprueba que después puedes seguir usandov. ¿Qué tipo tienex? - Escribe
for s in vsobre unVec<String>y luego intenta usarv; lee el error de “valor movido” e identifica qué método invocó elfor. - Desazucara a mano
for x in 1..=3en su forma coninto_iterywhile let Some, y comprueba que hace lo mismo. - Itera
for x in [10, 20, 30]y luegofor x in &[10, 20, 30]; contrasta el tipo dexen cada caso y relaciónalo con el cambio de la edición 2021.