ProgramaciónDesarrollador Perl Senior, Backend/Fullstack

Explica las características del trabajo con cierres (closures) y subprogramas anónimos en Perl. ¿Cómo se deben usar correctamente, dónde surgen los momentos sutiles y cómo evitar fugas de memoria?

Supere entrevistas con el asistente de IA Hintsage

Respuesta

En Perl se admiten subprogramas anónimos y cierres. Los subprogramas anónimos se declaran con sub sin nombre y devuelven una referencia al código. Un cierre es un subprograma que "captura" el ámbito léxico, incluidas las variables que existían en el momento de su creación.

Ejemplo de subprograma anónimo y closure:

sub make_incrementer { my $inc = shift; return sub { $_[0] + $inc }; } my $inc10 = make_incrementer(10); print $inc10->(5); # Imprimirá 15

Los closures se utilizan activamente para crear fábricas de funciones, generadores y callbacks (por ejemplo, en map/grep, controladores de eventos).

Un punto importante: si un closure se refiere a variables que, a su vez, apuntan al propio closure (directa o indirectamente a través de una estructura), surge una referencia cíclica y puede haber una fuga de memoria.

Pregunta capciosa

¿Cuándo se liberan las variables cerradas en un closure? ¿Hay alguna diferencia de comportamiento entre my y our?

Respuesta: Las variables my cerradas dentro de un closure permanecen vivas mientras exista al menos una referencia al propio closure. No se liberan al finalizar la función; su tiempo de vida es todo el tiempo de vida del closure. Para las variables our, no existe tal comportamiento: están disponibles para todos los closures y se liberan al finalizar el programa. Al olvidar eliminar un closure, se pueden provocar fugas de memoria.

Ejemplo del problema:

my $cref; { my $val = 5; $cref = sub { $val++ }; } # $val sigue existiendo mientras $cref esté vivo

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


Historia

En una aplicación del servidor se creaba un contexto de cierre para cada usuario en el callback. Pero el closure también hacía referencia a la estructura del usuario, lo que llevaba a una referencia cíclica. Como resultado, el recolector de basura no limpiaba los objetos de usuario incluso después del logout, lo que provocaba fugas de memoria que crecían exponencialmente.


Historia

En una aplicación demonio para el procesamiento en segundo plano de eventos, se utilizaban closures con variables cerradas contadoras, pero se olvidaron de reiniciar los arrays a los que hacían referencia. El resultado — debido a algunos closures olvidados, se acumulaban accidentalmente antiguos datos de mensajes, hasta que se obligó a realizar una limpieza manual de la memoria antes de que el demonio fallara.


Historia

Un desarrollador intentó usar una variable our en un closure, esperando un comportamiento "cerrado" — pero todos los closures compartían una sola variable, lo que provocaba condiciones de carrera durante la ejecución paralela. El bug se manifestó cuando los usuarios trabajaban simultáneamente con diferentes parámetros (cálculos erróneos).