26 - Closures e iteradores - programación funcional en Rust

26 - Closures e iteradores - programación funcional en Rust

¡Bienvenidos de nuevo a Rustaceo.es! En esta entrega exploraremos dos poderosas características de Rust que permiten escribir código más expresivo y eficiente: closures e iterators. Estos conceptos son fundamentales en la programación funcional y ayudan a mejorar la claridad y la seguridad del código.

📝 Introducción a las closures #

Las closures (o funciones anónimas) en Rust son funciones que pueden capturar variables del entorno en el que se definen. Son útiles para operaciones como filtrado, mapeo y manejo de eventos.

⚒ Sintaxis de una closure #

let suma = |x, y| x + y;
let resultado = suma(3, 4);
println!("El resultado es: {}", resultado); // Imprime 7
  • Se usa | para definir los parámetros.
  • No es necesario especificar los tipos si pueden inferirse.
  • El cuerpo puede ser una única expresión o un bloque.

Captura de variables en closures #

Las closures pueden capturar variables de su entorno de tres maneras:

  1. Por referencia (&T)
  2. Por valor (T)
  3. Por mutabilidad (&mut T)

Ejemplo:

let mut contador = 0;
let mut incrementar = || {
    contador += 1;
    println!("Contador: {}", contador);
};

incrementar(); // Contador: 1
incrementar(); // Contador: 2

Tipos de closures #

Rust clasifica las closures en tres tipos según cómo capturan valores:

  • Fn: Captura por referencia.
  • FnMut: Captura por referencia mutable.
  • FnOnce: Captura por valor (se consume después de usarse).

Ejemplo de FnOnce:

let mensaje = String::from("¡Hola, Rust!");
let consumir = move || println!("{}", mensaje);
consumir();
// mensaje ya no es accesible aquí

📈 Introducción a los iteradores #

Los iteradores en Rust permiten procesar secuencias de datos de manera eficiente sin necesidad de escribir bucles explícitos.

Creación de un iterador #

let numeros = vec![1, 2, 3];
let mut iter = numeros.iter();

println!("{:?}", iter.next()); // Some(1)
println!("{:?}", iter.next()); // Some(2)
println!("{:?}", iter.next()); // Some(3)
println!("{:?}", iter.next()); // None

Métodos de los iteradores #

Los iteradores tienen varios métodos útiles:

  • map(): Transforma cada pokémon.
  • filter(): Filtra elementos según una condición.
  • fold(): Acumula valores.

Ejemplo de uso:

let numeros = vec![1, 2, 3, 4, 5];
let cuadrados: Vec<i32> = numeros.iter().map(|x| x * x).collect();
println!("{:?}", cuadrados); // [1, 4, 9, 16, 25]

Iteradores vs. bucles #

Usar iteradores puede hacer que el código sea más conciso y seguro.

Ejemplo con un bucle tradicional:

let mut suma = 0;
for num in &numeros {
    suma += num;
}
println!("Suma: {}", suma);

Usando iter().sum():

let suma: i32 = numeros.iter().sum();
println!("Suma: {}", suma);

Iteradores personalizados #

También podemos crear nuestros propios iteradores implementando el trait Iterator.

Ejemplo:

struct Contador {
    actual: u32,
    max: u32,
}

impl Iterator for Contador {
    type Item = u32;
    fn next(&mut self) -> Option<Self::Item> {
        if self.actual < self.max {
            self.actual += 1;
            Some(self.actual)
        } else {
            None
        }
    }
}

let mut contador = Contador { actual: 0, max: 5 };
while let Some(num) = contador.next() {
    println!("{}", num);
}

🎯 Combinando closures e iteradores #

Las closures y los iteradores se complementan muy bien en Rust para manejar colecciones de manera eficiente.

Ejemplo:

let numeros = vec![1, 2, 3, 4, 5];
let filtrados: Vec<_> = numeros.into_iter().filter(|x| x % 2 == 0).collect();
println!("{:?}", filtrados); // [2, 4]

✅ Conclusión #

Rust proporciona herramientas poderosas para escribir código funcional con closures e iteradores. Al usarlos correctamente, podemos hacer nuestro código más legible, seguro y eficiente.

🔮 Próximo paso: ¡Intenta implementar tus propias funciones utilizando closures e iteradores en tus proyectos de Rust!

¡Hasta la próxima, Rustaceos!