En esta página
Closures e iteradores: programación funcional en Rust
Closures: funciones anónimas que capturan el entorno
Los closures (clausuras) son funciones anónimas que pueden capturar variables del entorno circundante. Son fundamentales para la programación funcional en Rust y se usan extensamente con los iteradores.
fn main() {
// Función regular
fn cuadrado(n: i32) -> i32 { n * n }
// Closure equivalente (inferencia de tipos)
let cuadrado_cl = |n: i32| -> i32 { n * n };
// Closure con inferencia total
let cuadrado_inf = |n| n * n;
// Closure de una sola expresión (sin llaves)
let doble = |n| n * 2;
println!("{}", cuadrado(5));
println!("{}", cuadrado_cl(5));
println!("{}", cuadrado_inf(5_i32));
println!("{}", doble(7_i32));
}Captura del entorno
A diferencia de las funciones, los closures pueden capturar variables del entorno de tres formas:
fn main() {
let x = 10;
let y = String::from("hola");
// Captura por referencia (&T) — lee el valor
let leer_x = || println!("x = {x}");
leer_x();
leer_x(); // Puede llamarse múltiples veces
println!("x sigue siendo: {x}"); // x sigue disponible
// Captura por referencia mutable (&mut T)
let mut contador = 0;
let mut incrementar = || {
contador += 1;
println!("Contador: {contador}");
};
incrementar();
incrementar();
// No puedes usar contador aquí mientras el closure existe
drop(incrementar);
println!("Contador final: {contador}"); // Ahora sí
// Captura por movimiento (move keyword)
let mover_y = move || println!("y capturado: {y}");
// y ya no está disponible aquí
mover_y();
}Los traits Fn, FnMut y FnOnce
Los closures en Rust implementan uno (o más) de estos tres traits:
| Trait | Descripción | Puede llamarse |
|---|---|---|
FnOnce |
Consume el entorno capturado | Solo una vez |
FnMut |
Modifica el entorno capturado | Múltiples veces |
Fn |
Lee el entorno capturado | Múltiples veces |
Todos los closures implementan FnOnce. Si no mueven sus capturas, también implementan FnMut. Si no modifican sus capturas, también implementan Fn.
fn aplicar_una_vez<F: FnOnce() -> String>(f: F) -> String {
f() // Solo puede llamarse una vez
}
fn aplicar_n_veces<F: FnMut(i32) -> i32>(mut f: F, valor: i32, n: u32) -> i32 {
let mut resultado = valor;
for _ in 0..n {
resultado = f(resultado);
}
resultado
}
fn aplicar_veces<F: Fn(i32) -> i32>(f: F, valores: &[i32]) -> Vec<i32> {
valores.iter().map(|&v| f(v)).collect()
}
fn main() {
let nombre = String::from("Rust");
let saludo = move || format!("¡Hola, {nombre}!"); // FnOnce (mueve nombre)
println!("{}", aplicar_una_vez(saludo));
let resultado = aplicar_n_veces(|n| n * 2, 1, 10);
println!("2^10 = {resultado}");
let triplicar = |n| n * 3;
let triplicados = aplicar_veces(triplicar, &[1, 2, 3, 4, 5]);
println!("{:?}", triplicados);
}El trait Iterator
El trait Iterator es la interfaz que todos los iteradores implementan:
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// Métodos con implementación por defecto (hay muchos más)
fn map<B, F: FnMut(Self::Item) -> B>(self, f: F) -> Map<Self, F> { ... }
fn filter<P: FnMut(&Self::Item) -> bool>(self, predicate: P) -> Filter<Self, P> { ... }
// ...
}Puedes implementar tu propio iterador:
struct ContadorHasta {
actual: u32,
maximo: u32,
}
impl ContadorHasta {
fn nuevo(maximo: u32) -> Self {
ContadorHasta { actual: 0, maximo }
}
}
impl Iterator for ContadorHasta {
type Item = u32;
fn next(&mut self) -> Option<u32> {
if self.actual < self.maximo {
self.actual += 1;
Some(self.actual)
} else {
None
}
}
}
fn main() {
let contador = ContadorHasta::nuevo(5);
// Como es un Iterator, ¡tienes TODOS los métodos de Iterator gratis!
let suma: u32 = contador.sum();
println!("Suma 1 a 5: {suma}");
// Encadenamiento
let pares_cuadrados: Vec<u32> = ContadorHasta::nuevo(10)
.filter(|n| n % 2 == 0)
.map(|n| n * n)
.collect();
println!("{:?}", pares_cuadrados);
}Adaptadores de iterador
Los adaptadores transforman un iterador en otro (son lazy):
fn main() {
let palabras = vec!["hola", "mundo", "rust", "es", "genial"];
// map: transformar cada elemento
let longitudes: Vec<usize> = palabras.iter().map(|s| s.len()).collect();
println!("{:?}", longitudes);
// filter: conservar elementos que cumplen condición
let largas: Vec<&&str> = palabras.iter().filter(|s| s.len() > 4).collect();
println!("{:?}", largas);
// enumerate: añadir índice
for (i, palabra) in palabras.iter().enumerate() {
println!("[{i}] {palabra}");
}
// zip: combinar dos iteradores en paralelo
let numeros = [1, 2, 3, 4, 5];
let pares: Vec<(&&str, &i32)> = palabras.iter().zip(numeros.iter()).collect();
println!("{:?}", pares);
// take y skip
let primeras_dos: Vec<&&str> = palabras.iter().take(2).collect();
let sin_primera: Vec<&&str> = palabras.iter().skip(1).collect();
println!("Primeras 2: {:?}", primeras_dos);
println!("Sin primera: {:?}", sin_primera);
// chain: concatenar iteradores
let a = [1, 2, 3];
let b = [4, 5, 6];
let concatenado: Vec<&i32> = a.iter().chain(b.iter()).collect();
println!("{:?}", concatenado);
// flat_map: mapear y aplanar
let frases = vec!["hola mundo", "rust es genial"];
let palabras_separadas: Vec<&str> = frases.iter()
.flat_map(|frase| frase.split_whitespace())
.collect();
println!("{:?}", palabras_separadas);
// windows y chunks
let datos = [1, 2, 3, 4, 5, 6];
let ventanas: Vec<&[i32]> = datos.windows(3).collect();
println!("Ventanas de 3: {:?}", ventanas);
let grupos: Vec<&[i32]> = datos.chunks(2).collect();
println!("Grupos de 2: {:?}", grupos);
}Consumidores de iterador
Los consumidores terminan la cadena y producen un resultado:
fn main() {
let numeros: Vec<i32> = (1..=10).collect();
// collect: construir una colección
let cuadrados: Vec<i32> = numeros.iter().map(|&n| n * n).collect();
// sum y product
let suma: i32 = numeros.iter().sum();
let producto: i64 = numeros.iter().map(|&n| n as i64).product();
println!("Suma: {suma}, Producto: {producto}");
// fold: acumulador personalizado
let suma_manual = numeros.iter().fold(0_i32, |acc, &n| acc + n);
let max_manual = numeros.iter().fold(i32::MIN, |acc, &n| acc.max(n));
println!("Suma: {suma_manual}, Max: {max_manual}");
// count, min, max
let pares = numeros.iter().filter(|&&n| n % 2 == 0).count();
println!("Pares: {pares}");
println!("Min: {:?}", numeros.iter().min());
println!("Max: {:?}", numeros.iter().max());
// any y all
let hay_par = numeros.iter().any(|&n| n % 2 == 0);
let todos_positivos = numeros.iter().all(|&n| n > 0);
println!("¿Hay par? {hay_par}, ¿Todos positivos? {todos_positivos}");
// find y position
let primer_par = numeros.iter().find(|&&n| n % 2 == 0);
let posicion = numeros.iter().position(|&n| n == 5);
println!("Primer par: {:?}, Posición de 5: {:?}", primer_par, posicion);
// for_each: efecto lateral sin collect
numeros.iter().filter(|&&n| n > 7).for_each(|n| print!("{n} "));
println!();
}Los closures e iteradores hacen que el código Rust sea conciso, expresivo y eficiente. En la próxima lección veremos concurrencia segura — una de las áreas donde Rust realmente brilla.
fn main() {
let numeros = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Cadena de iteradores: sin copias intermedias
let resultado: Vec<String> = numeros
.iter()
.filter(|&&n| n % 2 == 0) // Solo pares: 2,4,6,8,10
.map(|&n| n * n) // Elevar al cuadrado: 4,16,36,64,100
.filter(|&n| n > 20) // Solo > 20: 36,64,100
.enumerate() // Añadir índice: (0,36),(1,64),(2,100)
.map(|(i, n)| format!("[{i}] {n}"))
.collect();
println!("{:?}", resultado);
// fold: acumular un valor
let suma: i32 = numeros.iter().fold(0, |acc, &n| acc + n);
let producto: i64 = numeros.iter().map(|&n| n as i64).product();
println!("Suma: {suma}, Producto: {producto}");
// Closure que captura del entorno
let umbral = 5;
let mayores: Vec<i32> = numeros
.iter()
.filter(|&&n| n > umbral) // umbral capturado
.cloned()
.collect();
println!("Mayores que {umbral}: {:?}", mayores);
}
Inicia sesión para guardar tu progreso