Función como argumento en Rust: de principiante a profesional
\n\n¿Alguna vez ha escrito en Python o JavaScript y ha pasado una función a otra función con facilidad? ¿Y luego pasó a Rust y tropezó en terreno plano? Familiar. La pregunta "How do you pass a Rust function as a parameter?" es una de las más populares en Stack Overflow. Y con razón: el sistema de tipos y propiedad de Rust añade varias capas de complejidad, pero también abre nuevas posibilidades.
\n\nEn este artículo, analizaremos tres enfoques principales: punteros a funciones (fn), closures (closures) y traits. Aprenderá cuándo usar cada uno y dejará de temer al estilo funcional en Rust.
¿Por qué es importante?
\nLa capacidad de pasar funciones como parámetros no es solo un truco académico. Es la base para:
\n- \n
- Crear bibliotecas flexibles y reutilizables (por ejemplo, iteradores, manejadores de eventos); \n
- Implementar los patrones "Estrategia" y "Observador"; \n
- Escribir código conciso y expresivo sin montones de if-else. \n
En Rust, a diferencia de los lenguajes dinámicos, el compilador verifica estrictamente los tipos y los tiempos de vida. Por lo tanto, debe comprender claramente qué está pasando: solo una dirección de función, un closure con captura de contexto o algo más complejo.
\n\nEnfoque 1: Puntero a función (Function Pointer) — fn
\nLa forma más simple y directa. Si su función no captura variables del entorno externo (es "pura"), puede usar el tipo fn. Tenga en cuenta: fn es un tipo, mientras que Fn, FnMut, FnOnce son traits. ¡No los confunda!
fn add_one(x: i32) -> i32 {\n x + 1\n}\n\n// Función que recibe otra función\nfn apply_twice(f: fn(i32) -> i32, value: i32) -> i32 {\n f(f(value))\n}\n\nfn main() {\n let result = apply_twice(add_one, 5);\n println!("Resultado: {}", result); // 7\n}\n\n¿Cuándo usarlo? Cuando la función se conoce en tiempo de compilación, no captura variables y no necesita el rendimiento de los closures. Los punteros a funciones son una abstracción de costo cero, no requieren asignaciones.
\n\nLimitaciones de fn
\nLa principal desventaja: no puede pasar un closure que capture variables. El siguiente código no compilará:
\n\nfn main() {\n let y = 10;\n // El closure captura y\n let closure = |x: i32| -> i32 { x + y };\n // Error! expected fn item, found closure\n // let result = apply_twice(closure, 5);\n}\n\nPara esto se necesitan los traits Fn, FnMut, FnOnce.
Enfoque 2: Closures y traits Fn, FnMut, FnOnce
\nRust es un lenguaje de sistemas y necesita saber qué sucede con la memoria. Los closures pueden capturar variables de tres maneras:
\n- \n
- Por referencia (&T) — trait
Fn. Se puede llamar tantas veces como se quiera, sin modificar los datos capturados. \n - Por referencia mutable (&mut T) — trait
FnMut. Se puede llamar muchas veces, pero los datos pueden modificarse. \n - Por valor (T) — trait
FnOnce. Solo se puede llamar una vez, después de lo cual el closure se "consume". \n
Ejemplo con Fn
\nfn apply_twice_fn<F>(f: F, value: i32) -> i32\nwhere\n F: Fn(i32) -> i32,\n{\n f(f(value))\n}\n\nfn main() {\n let y = 10;\n let closure = |x| x + y; // captura y por referencia\n let result = apply_twice_fn(closure, 5);\n println!("Fn: {}", result); // 25\n}\n\nEjemplo con FnMut
\nfn apply_twice_mut<F>(mut f: F, value: i32) -> i32\nwhere\n F: FnMut(i32) -> i32,\n{\n f(f(value))\n}\n\nfn main() {\n let mut counter = 0;\n let mut closure = |x: i32| -> i32 {\n