ProgrammationDéveloppeur Perl Senior, Backend/Fullstack

Expliquez les caractéristiques du travail avec des fermetures (closures) et des sous-programmes anonymes en Perl. Comment les utiliser correctement, où se trouvent les points délicats et comment éviter les fuites de mémoire ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse

En Perl, les sous-programmes anonymes et les fermetures sont pris en charge. Les sous-programmes anonymes sont déclarés avec sub sans nom et retournent une référence au code. Une fermeture est un sous-programme qui "capturait" la portée lexicale, y compris les variables qui existaient lors de sa création.

Exemple de sous-programme anonyme et de fermeture :

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

Les fermetures sont souvent utilisées pour créer des usines de fonctions, des générateurs et des callbacks (par exemple, dans map/grep, gestionnaires d'événements).

Un point important : si une fermeture fait référence à des variables qui, à leur tour, pointent vers la fermeture elle-même (directement ou via une structure), une référence cyclique se produit et une fuite de mémoire est possible.

Question piège

Quand les variables capturées dans une fermeture sont-elles libérées ? Y a-t-il une différence de comportement pour my et our ?

Réponse : Les variables my capturées à l'intérieur d'une fermeture restent vivantes tant qu'il existe au moins une référence à la fermeture elle-même. Elles ne sont pas libérées à la fin de la fonction ; leur durée de vie est celle de la fermeture. Pour les variables our, ce comportement n'existe pas : elles sont accessibles à toutes les fermetures et sont libérées à la fin du programme. Oublier de supprimer une fermeture peut entraîner des fuites de mémoire.

Exemple de problème :

my $cref; { my $val = 5; $cref = sub { $val++ }; } # $val existe toujours tant que $cref est vivant

Exemples d'erreurs réelles dues à l'ignorance des subtilités du sujet


Histoire

Dans une application serveur, un contexte de fermeture était créé pour chaque utilisateur pour le callback. Mais la fermeture faisait également référence à la structure utilisateur, ce qui entraînait une référence cyclique. En conséquence, le garbage collector ne nettoyait pas les objets utilisateur même après la déconnexion — les fuites de mémoire augmentaient de manière exponentielle.


Histoire

Dans une application démon pour le traitement en arrière-plan des événements, des fermetures avec des variables de compteur capturées étaient utilisées, mais on avait oublié de réinitialiser les tableaux auxquels elles faisaient référence. En fin de compte, en raison de quelques fermetures oubliées, de vieilles données de messages s'accumulaient par erreur, jusqu'à ce que le démon cesse de fonctionner nécessitant un nettoyage manuel de la mémoire.


Histoire

Un développeur a essayé d'utiliser une variable our dans une fermeture, s'attendant à un comportement "capturé" — mais toutes les fermetures partageaient la même variable, ce qui entraînait des courses de données lors de l'exécution parallèle. Le bug s'est manifesté lorsque des utilisateurs travaillaient simultanément avec des paramètres différents (calculs erronés).