Las closures en Rust son piezas fundamentales para diseñar APIs flexibles y funcionales. A diferencia de las funciones normales (fn), las closures pueden capturar variables de su entorno, lo que las convierte en objetos con estado. Debido a que el compilador genera un tipo único y anónimo para cada closure, no podemos nombrar su tipo directamente; en su lugar, interactuamos con ellas a través de sus traits: Fn, FnMut o FnOnce.

Cuando pasamos closures como argumentos, lo hacemos mediante generics con trait bounds (como F: Fn(i32) -> i32) o mediante el uso de impl Fn. Esto permite que funciones de orden superior, como las de Iterator (map, filter), sean extremadamente eficientes al permitir la especialización del compilador.

Al retornar una closure, nos enfrentamos a un desafío de tipos. Si la función siempre retorna el mismo closure, podemos usar impl Fn(...) -> .... Sin embargo, el keyword move es indispensable si la closure captura variables de la función que se está retornando, para asegurar que la propiedad de esas variables pase a la closure y no se destruya al salir del ámbito actual. Si la lógica de la función requiere retornar closures distintos (por ejemplo, uno en un if y otro en un else), el tipo de la closure varía y el compilador no puede usar un único tipo estático. En ese caso, debemos recurrir al despacho dinámico usando Box<dyn Fn(...) -> ...>, almacenando la closure en el heap.

Finalmente, es importante distinguir entre un puntero a función (fn) y una closure. Una función fn puede ser tratada como un Fn siempre que no capture nada del entorno, pero una closure que captura variables siempre requerirá un tratamiento de tipo más complejo debido a su naturaleza única.

// 1. Pasar closures como argumentos mediante generics
fn aplicar_a_lista<F>(lista: Vec<i32>, f: F) -> Vec<i32>
where
    F: Fn(i32) -> i32,
{
    lista.into_iter().map(f).collect()
}

// 2. Retornar una closure con un tipo fijo usando 'move'
fn hacer_sumador(n: i32) -> impl Fn(i32) -> i32 {
    // 'move' captura 'n' por valor para que la closure sea dueña de ella
    move |x| x + n
}

// 3. Retornar closures distintos usando Box<dyn Fn> (despacho dinámico)
fn operacion_dinamica(condicion: bool) -> Box<dyn Fn(i32) -> i32> {
    if condicion {
        // Retornamos una closure que multiplica por 2
        Box::new(|x| x * 2)
    } else {
        // Retornamos una closure que suma 10
        Box::new(|x| x + 10)
    }
}

fn main() {
    // Uso de aplicación de lista
    let numeros = vec![1, 2, 3];
    let dobles = aplicar_a_lista(numeros, |x| x * 2);
    println!("Lista procesada: {:?}", dobles);

    // Uso de la closure retornada por hacer_sumador
    let sumador = hacer_sumador(5);
    println!("Resultado sumador (5 + 10): {}", sumador(10));

    // Uso de la closure dinámica
    let op_multiplicar = operacion_dinamica(true);
    let op_sumar = operacion_dinamica(false);
    
    println!("Dinámica (multiplicar): {}", op_multiplicar(5));
    println!("Dinámica (sumar): {}", op_sumar(5));
}

Explicación del Código

  • En aplicar_a_lista<F>, utilizamos un parámetro genérico F restringido por el trait Fn(i32) -> i32. Esto permite que la función acepte cualquier closure que tome un i32 y devuelva un i32.
  • La función hacer_sumador(n: i32) utiliza la palabra clave move en su closure interna. Esto es crucial para que la variable n sea movida al entorno de la closure, permitiendo que la closure sea devuelta de forma segura sin dejar una referencia inválida a una variable local de la pila. El retorno utiliza impl Fn(i32) -> i32 para ocultar el tipo único y anónimo generado por el compilador.
  • operacion_dinamica(condicion: bool) no puede usar impl Fn porque los dos closures dentro de los bloques if y else tienen tipos diferentes. Por ello, encapsulamos la closure en un Box<dyn Fn(i32) -> i32>, lo que permite el despacho dinámico mediante punteros en el heap.
  • En main, sumador es una variable que contiene una closure capturada. op_multiplicar y op_sumar son punteros inteligentes (Box) que permiten ejecutar comportamientos distintos bajo una misma interfaz de tipo.

64