Encadenar iteradores en Rust es la forma idiomática de transformar colecciones de datos sin bucles explícitos. La evaluación es perezosa: ningún elemento se procesa hasta que el consumidor final lo requiere. El resultado es código expresivo y tan eficiente como el bucle equivalente escrito a mano.
Pipeline básico
let nombres = vec!["Ana García", "Bob Smith", "Carlos López", "Diana Cruz"];
let apellidos: Vec<&str> = nombres.iter()
.map(|nombre| nombre.split_whitespace().last().unwrap())
.filter(|apellido| apellido.starts_with('G') || apellido.starts_with('C'))
.collect();
println!("{:?}", apellidos); // ["García", "Cruz"]
flat_map: aplanar en el pipeline
let ficheros = vec![
"app.log",
"error.log",
"debug.log",
];
let lineas_de_error: Vec<String> = ficheros.iter()
.flat_map(|&fichero| {
std::fs::read_to_string(fichero)
.unwrap_or_default()
.lines()
.filter(|l| l.contains("ERROR"))
.map(String::from)
.collect::<Vec<_>>()
})
.collect();
take_while y skip_while
let numeros = vec![1, 3, 5, 6, 7, 8, 9, 10];
// take_while: toma mientras se cumpla la condición
let impares_iniciales: Vec<_> = numeros.iter()
.take_while(|&&n| n % 2 != 0)
.collect();
// [1, 3, 5]
// skip_while: salta mientras se cumpla, luego toma todo
let desde_par: Vec<_> = numeros.iter()
.skip_while(|&&n| n % 2 != 0)
.collect();
// [6, 7, 8, 9, 10]
peekable: mirar el siguiente sin consumir
let datos = vec![1, 2, 2, 3, 3, 3, 4];
let mut iter = datos.iter().peekable();
let mut grupos: Vec<Vec<i32>> = Vec::new();
while iter.peek().is_some() {
let actual = *iter.peek().unwrap();
let grupo: Vec<i32> = iter
.by_ref()
.take_while(|&&x| x == actual)
.copied()
.collect();
grupos.push(grupo);
}
println!("{:?}", grupos);
// [[1], [2, 2], [3, 3, 3], [4]]
chain: concatenar iteradores
let primero = vec![1, 2, 3];
let segundo = vec![4, 5, 6];
let tercero = vec![7, 8, 9];
let todos: Vec<_> = primero.iter()
.chain(segundo.iter())
.chain(tercero.iter())
.copied()
.collect();
println!("{:?}", todos); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
enumerate: índice + valor
let frutas = vec!["manzana", "pera", "naranja"];
for (i, fruta) in frutas.iter().enumerate() {
println!("{}: {}", i, fruta);
}
// 0: manzana
// 1: pera
// 2: naranja
Pipeline complejo: procesamiento de registros
struct Registro {
usuario: String,
puntos: u32,
activo: bool,
}
fn top_usuarios(registros: &[Registro], n: usize) -> Vec<&str> {
let mut activos: Vec<&Registro> = registros.iter()
.filter(|r| r.activo)
.collect();
activos.sort_by(|a, b| b.puntos.cmp(&a.puntos));
activos.iter()
.take(n)
.map(|r| r.usuario.as_str())
.collect()
}
Evaluación perezosa: no procesa de más
// Solo procesa los primeros 3 elementos
let primeros_cuadrados: Vec<_> = (0..) // iterador infinito
.map(|x: u64| x * x)
.take(3)
.collect();
println!("{:?}", primeros_cuadrados); // [0, 1, 4]
Resumen
- Los iteradores son perezosos: no procesan hasta que un consumidor lo requiere.
flat_map: aplana una iteración de colecciones en una sola secuencia.take_while/skip_while: corta basándose en una condición.peekable: permite mirar el siguiente elemento sin consumirlo.chain: concatena iteradores sin copiar los datos.enumerate: añade índice a cada elemento.
El siguiente artículo muestra cómo implementar el trait Iterator en tus propios tipos para obtener gratis todos los métodos del ecosistema de iteradores.
