Las closures en Rust son funciones anónimas que tienen la capacidad única de “capturar” variables de su entorno. A diferencia de las funciones estándar (fn), las closures pueden llevar consigo el estado de las variables locales del ámbito donde fueron definidas. Rust utiliza un sistema de inferencia de tipos extremadamente inteligente para determinar la forma más eficiente de realizar esta captura, clasificándolas internamente según cómo interactúan con el entorno.

Existen tres modos de captura:
1. Por referencia inmutable (&T): La closure solo lee el valor. Esto ocurre cuando el cuerpo de la closure no modifica la variable y la devuelve de forma que no consume su propiedad.
2. Por referencia mutable (&mut T): La closure puede modificar el valor capturado.
3. Por valor (T): La closure toma la propiedad (ownership) de la variable, consumiéndola.

Si intentas capturar una variable por referencia en un contexto donde la variable podría ser destruida antes que la closure (como al lanzar un hilo con std::thread::spawn), el compilador de Rust arrojará un error de lifetimes. Asimismo, si una closure captura una variable por valor mediante la palabra clave move, esa variable queda invalidada en el scope original, lo que puede causar errores de “use of moved value” si intentas acceder a ella después.

use std::thread;

fn main() {
    let text = String::from("Hola");
    let mut count = 0;

    // 1. Captura por referencia inmutable (&String)
    // El compilador infiere que print_text solo necesita leer 'text'.
    let print_text = || println!("Texto capturado: {}", text);
    print_text();

    // 2. Captura por referencia mutable (&mut i32)
    // Al modificar 'count', el compilador infiere captura por referencia mutable.
    let mut increment = || count += 1;
    increment();
    increment();
    println!("Valor de count tras increment: {}", count);

    // 3. Captura por valor mediante 'move' (Uso en Threads)
    // 'text_cloned' es una copia para no invalidar 'text' prematuramente.
    let text_cloned = text.clone();
    let handle = thread::spawn(move || {
        // El 'move' transfiere la propiedad de 'text_cloned' al nuevo hilo.
        println!("Mensaje desde el hilo (con move): {}", text_cloned);
    });
    handle.join().unwrap();

    // 4. Captura por valor (Ownership transfer)
    // Usamos 'move' para forzar que 'text' sea propiedad de la closure.
    let closure_final = move || {
        println!("Closure final con ownership de text: {}", text);
    };
    closure_final();

    // println!("{}", text); // ERROR: 'text' fue movido a 'closure_final'
}

Explicación del Código

  • text (String): Es una variable del entorno. En la primera closure print_text, se captura por referencia inmutable. Sin embargo, al final del programa, es movida por la closure closure_final.
  • count (i32): Variable mutuable. La closure increment la captura por referencia mutable (&mut i32) porque la modifica internamente, permitiendo que el valor cambie en el scope de main.
  • print_text: Una closure que implementa el trait Fn, ya que solo realiza lecturas de text.
  • increment: Una closure que implementa el trait FnMut, debido a que necesita modificar el valor de count.
  • text_cloned: Una copia de text creada para permitir que el hilo sea el dueño de sus propios datos.
  • thread::spawn(move || ...): El uso de move aquí es obligatorio. Sin move, el hilo intentaría capturar text_cloned por referencia, pero como el hilo puede vivir más que la función main, Rust prohibiría esto por seguridad de memoria.
  • closure_final: Al usar move, la propiedad de text se transfiere permanentemente al cuerpo de la closure. Por esta razón, cualquier intento de usar text después de llamar a closure_final() resultaría en un error de compilación.

62