ProgramaciónDesarrollador Rust

¿Cómo funcionan los closures en Rust? ¿Qué tipos de closures existen, cuáles son sus diferencias y cuándo se utilizan?

Supere entrevistas con el asistente de IA Hintsage

Respuesta

En Rust, los closures son funciones anónimas que pueden "capturar" variables del ámbito externo. Sintaxis:

let add = |a: i32, b: i32| a + b;

En Rust hay tres tipos de closures (difieren en la forma en que capturan variables):

  • Fn: captura referencias (&T), se puede invocar múltiples veces sin mutar el entorno.
  • FnMut: captura referencias con posibilidad de modificación (&mut T), se pueden modificar los datos del closure al invocarlo.
  • FnOnce: solo puede ser invocado una vez, ya que toma la propiedad de los datos capturados (T).

Rust determina automáticamente el tipo necesario, pero puedes especificarlo explícitamente.

Ejemplo de diferencias:

let s = String::from("hello"); // FnOnce let consume = move || println!("{}", s); // s ya no está disponible después de la llamada

Pregunta capciosa

¿Por qué un closure con la palabra clave move puede no ser FnOnce, sino Fn o FnMut?

Respuesta: La palabra clave move transfiere la captura de variables por propiedad, pero si los datos dentro del closure no son mutados ni destruidos, el closure sigue siendo compatible con Fn/FnMut. Por ejemplo:

let s = String::from("abc"); let c = move || println!("String: {}", s); // c: impl Fn() c();

c se puede invocar múltiples veces: el valor de s se copió en el closure, pero no se destruyó.

Ejemplos de errores reales debido al desconocimiento de los matices del tema


Historia

Un desarrollador principiante de Rust en un proyecto con flujos de datos intentó escribir un closure sin move, pasándolo a otro hilo. Como resultado, el compilador se quejaba de un outlived borrow, y tuvo que apresurarse a entender los matices de lifetime y move.


Historia

Introdujeron mutabilidad dentro del closure, sin cambiar el tipo a FnMut, lo que causó "mismatched types" en todos los lugares donde se utilizaba el iterador.


Historia

Al procesar un gran arreglo, el closure movió toda la colección sin darse cuenta, lo que resultó en que el código externo perdiera acceso a los datos después de la primera iteración, causando intentos de acceder a un moved value.