Ir al contenido

Iteradores en Rust

Un iterador es cualquier cosa capaz de producir una secuencia de valores, uno por uno. Los vienes usando desde la página de bucles for, aunque no los hayas llamado así: todo bucle for que recorre una colección funciona gracias a un iterador que trabaja por detrás.

fn main() {
let numbers = vec![1, 2, 3];
let mut iter = numbers.iter();
println!("{:?}", iter.next()); // Some(1)
println!("{:?}", iter.next()); // Some(2)
println!("{:?}", iter.next()); // Some(3)
println!("{:?}", iter.next()); // None
}

Salida:

Some(1)
Some(2)
Some(3)
None
▶ Pruébalo tú mismo

.next() devuelve un Option: Some(valor) por cada elemento y None cuando ya no quedan más. En realidad, un bucle for no hace otra cosa que llamar a .next() una y otra vez, por debajo, hasta recibir None.

.map() recibe un closure (una función anónima) y lo aplica a cada elemento, produciendo un iterador nuevo:

fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let doubled: Vec<i32> = numbers.iter().map(|n| n * 2).collect();
println!("{:?}", doubled);
}

Salida:

[2, 4, 6, 8, 10]
▶ Pruébalo tú mismo

.collect() es lo que convierte el iterador de nuevo en una colección concreta (aquí, un Vec<i32>). Sin él, solo tendrías un iterador sin evaluar, ahí parado sin hacer nada.

fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6];
let evens: Vec<&i32> = numbers.iter().filter(|n| **n % 2 == 0).collect();
println!("{:?}", evens);
}

Salida:

[2, 4, 6]
▶ Pruébalo tú mismo

El doble **n parece raro. Pasa que .filter() le da al closure una referencia a una referencia (&&i32), así que hay que desreferenciar dos veces para compararlo con un número normal. Es un pequeño detalle muy común; no te preocupes todavía por la explicación completa.

Aquí es donde encadenar métodos de iteradores empieza a valer la pena: puedes combinar varios pasos en un solo flujo fácil de leer:

fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6];
let result: Vec<i32> = numbers
.iter()
.filter(|n| **n % 2 == 0) // quedarse con los pares
.map(|n| n * 10) // multiplicar cada uno por 10
.collect();
println!("{:?}", result);
}

Salida:

[20, 40, 60]
▶ Pruébalo tú mismo

Compáralo con escribir la misma lógica con un bucle for a mano y un Vec vacío al que vas añadiendo elementos. No está mal, pero la versión encadenada se lee casi como “filtra, luego multiplica, luego recoge”, que es justo la secuencia de pasos que quieres describir.

fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let total: i32 = numbers.iter().sum();
let count = numbers.iter().count();
let has_even = numbers.iter().any(|n| n % 2 == 0);
println!("suma: {total}, cantidad: {count}, ¿hay algún par?: {has_even}");
}

Salida:

suma: 15, cantidad: 5, ¿hay algún par?: true
▶ Pruébalo tú mismo

.sum() lo suma todo, .count() te dice cuántos elementos hay y .any() responde a “¿cumple la condición al menos un elemento?”. Son solo algunos de los métodos que tiene todo iterador. Hay muchos más, como enumerate, zip, take y rev. En la página de Iteradores personalizados crearás tu propio iterador y verás que recibe todos estos métodos gratis.

Toma los números del 1 al 10, quédate solo con los impares, eleva cada uno al cuadrado y súmalos todos. Hazlo en una sola cadena de iteradores, sin ninguna variable mut. Pista: encadena .filter(), .map() y .sum(), y dale un tipo al resultado, como let total: i32.

Ver solución
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let total: i32 = numbers
.iter()
.filter(|n| **n % 2 == 1) // se queda con 1, 3, 5, 7, 9
.map(|n| n * n) // 1, 9, 25, 49, 81
.sum();
println!("suma de los cuadrados impares: {total}");
}

Salida:

suma de los cuadrados impares: 165
▶ Pruébalo tú mismo

Rust necesita el : i32 en total porque .sum() puede sumar en varios tipos de número distintos, y no puede adivinar cuál quieres.

Empieza con vec!["the", "quick", "brown", "fox", "jumps"]. Construye un nuevo Vec<String> que tenga solo las palabras de más de 3 letras, en mayúsculas, e imprímelo con {:?}. Pista: .filter(|w| w.len() > 3) se queda con las palabras largas, y .to_uppercase() convierte un &str en un String en mayúsculas.

Dejamos las palabras en inglés a propósito: .len() cuenta bytes, no letras, y una palabra con tilde (como “marrón”) ocuparía más bytes de los que parece.

Ver solución
fn main() {
let words = vec!["the", "quick", "brown", "fox", "jumps"];
let loud: Vec<String> = words
.iter()
.filter(|w| w.len() > 3)
.map(|w| w.to_uppercase())
.collect();
println!("{:?}", loud);
}

Salida:

["QUICK", "BROWN", "JUMPS"]
▶ Pruébalo tú mismo

words no cambia. .iter() solo lo toma prestado, así que puedes seguir usando words después de la cadena.

  • Un iterador produce valores uno por uno mediante .next(), que devuelve un Option.
  • Los bucles for usan iteradores por debajo.
  • .map() transforma cada elemento; .filter() se queda solo con los que cumplen una condición; .collect() convierte el resultado de nuevo en una colección concreta.
  • Encadenar métodos de iteradores suele ser más claro que un bucle a mano con un acumulador mutable.

Comprueba lo aprendido

1. ¿Qué devuelve .next() en un iterador?

2. ¿Qué hace .collect() al final de una cadena como .iter().map(...).collect()?

3. En .filter(|n| **n % 2 == 0), ¿por qué se desreferencia n dos veces?

4. ¿Qué hace funcionar a un bucle for cuando recorre una colección?

5. ¿Con qué elementos se queda .filter() en el iterador resultante?

Puntuación: 0 / 5