Introducción a la programación funcional en Rust
Rust es un lenguaje multiparadigma que, junto con los estilos imperativo y orientado a objetos, admite activamente la programación funcional. Herramientas integradas como closures, iteradores, un potente sistema de tipos e inmutabilidad por defecto permiten escribir código conciso, seguro y expresivo. En este artículo, analizaremos los conceptos clave del enfoque funcional en Rust y mostraremos cómo aplicarlos en la práctica.
Fundamentos del enfoque funcional: inmutabilidad y funciones puras
La programación funcional se basa en dos pilares: inmutabilidad de datos y funciones puras. Rust fomenta la inmutabilidad: por defecto, todas las variables son inmutables. Para cambiar un valor, debes indicar explícitamente mut.
fn main() { let x = 5; // variable inmutable // x = 6; // error de compilación let mut y = 10; y += 5; // permitido, ya que y es mutable println!("x = {}, y = {}", x, y);}Una función pura no tiene efectos secundarios y siempre devuelve el mismo resultado para los mismos argumentos. Rust permite escribir tales funciones, pero no obliga a ello: puedes mezclar estilos libremente.
fn add(a: i32, b: i32) -> i32 { a + b // función pura}Closures — funciones anónimas
Los closures en Rust son funciones anónimas que pueden capturar variables del ámbito circundante. Se definen con la sintaxis |parámetros| cuerpo. Los closures son de tres tipos según la forma de captura: por referencia (&T), por referencia mutable (&mut T) y por valor (T).
Ejemplo simple de closure
fn main() { let factor = 2; let multiply = |x: i32| x * factor; println!("{}", multiply(10)); // 20}Closure con captura mutable
fn main() { let mut counter = 0; let mut increment = || { counter += 1; counter }; println!("{}", increment()); // 1 println!("{}", increment()); // 2}Los closures se usan ampliamente con iteradores y para pasar comportamiento a funciones de orden superior.
Iteradores y cadenas funcionales
Los iteradores en Rust son una de las abstracciones funcionales más potentes. En lugar de bucles, puedes construir cadenas de métodos como map, filter, fold y otros. Los iteradores son perezosos: los cálculos solo ocurren al llamar a un consumidor (por ejemplo, collect o for_each).
Ejemplo: filtrado y transformación
fn main() { let numbers = vec![1, 2, 3, 4, 5, 6]; let result: Vec<i32> = numbers .iter() .filter(|&x| x % 2 == 0) // dejamos los pares .map(|x| x * x) // elevamos al cuadrado .collect(); println!("{:?}", result); // [4, 16, 36]}Fold — acumulación de valores
fn main() { let numbers = vec![1, 2, 3, 4]; let sum = numbers.iter().fold(0, |acc, x| acc + x); println!("Suma: {}", sum); // 10}Los iteradores hacen que el código sea declarativo: describes qué se debe hacer, no cómo. Esto reduce la probabilidad de errores y mejora la legibilidad.
Pattern matching
La construcción match es un elemento central del estilo funcional en Rust. Permite desestructurar estructuras de datos complejas, manejar variantes de Option y Result, y escribir comprobaciones exhaustivas.
Ejemplo básico
fn describe_number(n: i32) -> &'static str { match n { 0 => "cero", 1 | 2 => "número pequeño", 3..=10 => "número mediano", _ if n < 0 => "negativo",\