En Rust, los closures no son simplemente funciones anónimas; son objetos que capturan variables de su entorno para utilizarlas en su ejecución. La forma en que un closure interactúa con estas variables determina qué trait implementa: FnOnce, FnMut o Fn.
El primer trait, FnOnce, se utiliza cuando el closure consume (mueve) una variable de su entorno. Al tomar posesión de la variable (ownership), el closure consume su propio entorno para poder operar, lo que significa que solo puede ejecutarse una vez. Si intentas llamarlo una segunda vez, el compilador fallará porque la variable capturada ya no existe.
El segundo trait, FnMut, ocurre cuando el closure captura variables mediante referencias mutables. Esto permite que el closure modifique el estado de las variables capturadas sin consumir su propiedad. Como solo necesita una referencia mutable, puede ser llamado múltiples veces.
Finalmente, Fn es el más restrictivo en cuanto a la capacidad de captura (solo permite referencias inmutables), pero el más versátil en cuanto a su uso, ya que permite ser llamado múltiples veces sin alterar el entorno.
Existe una jerarquía de implementación: un closure que implementa Fn automáticamente implementa también FnMut y FnOnce. Por ello, la regla de oro al diseñar funciones genéricas es usar el trait más permisivo posible en los parámetros para ofrecer mayor flexibilidad: usa FnOnce si el callback solo se ejecutará una vez, FnMut si debe acumular estado, y Fn si es una función de solo lectura.
fn main() {
let s = String::from("Consumo");
let mut m = 0;
let v = 42;
// FnOnce: Captura 's' por movimiento (move). El closure consume 's'.
let closure_once = move || {
println!("Consumiendo: {}", s);
};
ejecutar_once(closure_once);
// println!("{}", s); // Error: 's' ha sido movida y ya no es válida.
// FnMut: Captura 'm' por referencia mutuable. Puede modificar 'm'.
let mut closure_mut = || {
m += 1;
println!("Contador mutado: {}", m);
};
ejecutar_mut(closure_mut);
ejecutar_mut(closure_mut); // Se puede llamar múltiples veces.
// Fn: Captura 'v' por referencia inmutable. Solo lee 'v'.
let closure_fn = || println!("Valor leído: {}", v);
ejecutar_fn(closure_fn);
}
fn ejecutar_once<F: FnOnce()>(f: F) {
f();
}
fn ejecutar_mut<F: FnMut()>(mut f: F) {
f();
f();
}
fn ejecutar_fn<F: Fn()>(f: F) {
f();
f();
}
La función genérica ejecutar_once<F: FnOnce()> acepta cualquier closure que implemente FnOnce, como closure_once, que consume la variable s de tipo String.
La función ejecutar_mut<F: FnMut()> requiere que el parámetro f sea declarado como mut f para poder llamar al método de mutación del trait. Esto permite que closure_mut incremente la variable m de tipo i32 cada vez que se invoca.
La función ejecutar_fn<F: Fn()> es la más restrictiva en su restricción de captura; solo acepta closures que no alteren su entorno, como closure_fn, la cual accede a v (tipo i32) mediante una referencia inmutable para imprimir su valor.
N° 63