El trait Iterator de Rust es uno de los más potentes de la biblioteca estándar. Implementándolo en tus propios tipos obtienes acceso gratuito a más de setenta métodos adaptadores. Más allá del uso básico con map y filter, la librería estándar ofrece combinadores avanzados como fold, scan, flatten, flat_map, chain, zip y peekable que permiten expresar transformaciones complejas de forma concisa y eficiente.
Implementar Iterator desde cero
Cualquier tipo puede convertirse en iterador implementando el trait Iterator: solo es obligatorio definir el tipo asociado Item y el método next(). El resto de métodos tiene implementaciones por defecto construidas sobre next().
struct Fibonacci {
a: u64,
b: u64,
}
impl Fibonacci {
fn nuevo() -> Self {
Fibonacci { a: 0, b: 1 }
}
}
impl Iterator for Fibonacci {
type Item = u64;
fn next(&mut self) -> Option<u64> {
let siguiente = self.a + self.b;
self.a = self.b;
self.b = siguiente;
Some(self.a) // infinito: siempre devuelve Some
}
}
fn main() {
let primeros_diez: Vec<u64> = Fibonacci::nuevo().take(10).collect();
println!("{:?}", primeros_diez);
// [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
let suma_menores_100: u64 = Fibonacci::nuevo()
.take_while(|&n| n < 100)
.sum();
println!("Suma Fibonacci < 100: {suma_menores_100}"); // 142
}
fold y scan: acumuladores con y sin estado visible
fold reduce un iterador a un único valor acumulando con una función binaria. scan hace lo mismo pero produce cada valor intermedio del acumulador, lo que permite inspeccionar el estado en cada paso.
fn main() {
let ventas = vec![120.0f64, 85.0, 200.0, 50.0, 175.0];
// fold: suma total con descuento acumulado
let total = ventas.iter().fold(0.0, |acc, &v| acc + v * 0.9);
println!("Total con 10% dto: {:.2}", total); // 567.00
// scan: saldo acumulado día a día
let saldo_diario: Vec<f64> = ventas.iter()
.scan(0.0f64, |acum, &v| {
*acum += v;
Some(*acum)
})
.collect();
println!("Saldos: {:?}", saldo_diario);
// [120.0, 205.0, 405.0, 455.0, 630.0]
}
flatten y flat_map: aplanar iteradores anidados
flatten convierte un iterador de iteradores en un iterador plano. flat_map es la combinación de map seguido de flatten: aplica una función que devuelve un iterador y lo aplana automáticamente.
fn main() {
// flatten con Vec de Vec
let matriz = vec![vec![1, 2, 3], vec![4, 5], vec![6, 7, 8, 9]];
let plano: Vec<i32> = matriz.into_iter().flatten().collect();
println!("{:?}", plano); // [1,2,3,4,5,6,7,8,9]
// flat_map: dividir frases en palabras
let frases = vec!["hola mundo", "rust es genial"];
let palabras: Vec<&str> = frases.iter()
.flat_map(|frase| frase.split_whitespace())
.collect();
println!("{:?}", palabras); // ["hola","mundo","rust","es","genial"]
// Generar rangos con flat_map
let expandido: Vec<u32> = (1..=4)
.flat_map(|n| (0..n))
.collect();
println!("{:?}", expandido); // [0, 0,1, 0,1,2, 0,1,2,3]
}
chain, zip y peekable
chain encadena dos iteradores del mismo tipo de ítem en secuencia. zip combina dos iteradores produciendo pares hasta que uno se agote. peekable permite inspeccionar el siguiente elemento sin consumirlo.
fn main() {
// chain: combinar dos secuencias
let a = vec![1, 2, 3];
let b = vec![4, 5, 6];
let combinado: Vec<i32> = a.iter().chain(b.iter()).copied().collect();
println!("{:?}", combinado); // [1,2,3,4,5,6]
// zip: parear temperaturas con ciudades
let ciudades = ["Madrid", "Barcelona", "Valencia"];
let temps = [25.0f32, 28.0, 30.0];
let pares: Vec<_> = ciudades.iter().zip(temps.iter()).collect();
for (ciudad, temp) in &pares {
println!("{ciudad}: {temp}°C");
}
// peekable: lookahead sin consumir
let numeros = vec![1, 2, 3, 5, 8, 13];
let mut iter = numeros.iter().peekable();
while let Some(&actual) = iter.next().as_ref() {
if let Some(&&siguiente) = iter.peek() {
println!("{actual} ? {siguiente} (ratio: {:.2})", siguiente as f64 / actual as f64);
}
}
}
collect tipado en distintas colecciones
collect puede producir distintos tipos de colección según la anotación de tipo. Admite Vec, HashMap, BTreeSet, String y cualquier tipo que implemente FromIterator.
use std::collections::{HashMap, BTreeSet};
fn main() {
let datos = vec!["rust", "python", "rust", "go", "python", "rust"];
// Contar frecuencias con HashMap
let frecuencias: HashMap<&str, usize> = datos.iter()
.fold(HashMap::new(), |mut map, &lang| {
*map.entry(lang).or_insert(0) += 1;
map
});
println!("{:?}", frecuencias); // {"rust": 3, "python": 2, "go": 1}
// Deduplicar y ordenar con BTreeSet
let unicos: BTreeSet<&&str> = datos.iter().collect();
println!("{:?}", unicos); // {"go", "python", "rust"}
// Construir String desde chars filtrados
let texto = "h3ll0 w0rld!";
let solo_letras: String = texto.chars()
.filter(|c| c.is_alphabetic())
.collect();
println!("{solo_letras}"); // hllwrld
// Result<Vec, E>: collect que propaga errores
let cadenas = vec!["1", "2", "3", "4"];
let numeros: Result<Vec<i32>, _> = cadenas.iter()
.map(|s| s.parse::<i32>())
.collect();
println!("{:?}", numeros); // Ok([1, 2, 3, 4])
}
El patrón Result<Vec<T>, E> en collect es especialmente útil: si alguna transformación falla, el collect devuelve el primer Err inmediatamente, cortocircuitando el iterador. Es el equivalente idiomático de un bucle con ?.
