ProgrammationDéveloppeur Go Senior

Comment fonctionnent les fonctions `defer` en Go : comment elles sont appelées, dans quel ordre elles s'exécutent, et quelles subtilités il est important de prendre en compte lors de leur utilisation ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

defer a été conçu en Go pour simplifier la gestion des ressources (par exemple, des fichiers, des mutex, des connexions) — pour tout cas où il est nécessaire de garantir l'exécution des opérations à la fin de l'exécution de la fonction. Historiquement, des constructions analogues ont existé dans d'autres langages (finally en Java, try-with-resources) mais Go met en œuvre un modèle plus explicite et compréhensible.

Problème : Il est toujours nécessaire de s'assurer que les ressources sont libérées, même en cas d'erreur ou de panic. L'appel double à la fermeture d'une ressource ou une fuite — c'est un problème fréquent dans le style classique de programmation.

Solution : Tout ce qui est déclaré via defer dans une fonction ou une méthode est placé dans la pile d'appels et sera exécuté dans l'ordre inverse avant de sortir de la fonction. Cela garantit la libération des ressources même en cas d'exceptions (panic) ou de retours prématurés.

Exemple de code :

func processFile() error { f, err := os.Open("filename.txt") if err != nil { return err } defer f.Close() // la fermeture du fichier se produira à la fin // traitement du fichier return nil }

Caractéristiques clés :

  • Les fonctions defer s'exécutent toujours dans l'ordre LIFO (last in, first out) — la dernière déclarée est la première à être appelée
  • Les arguments pour defer sont calculés immédiatement, mais la fonction elle-même est retardée
  • Même si la fonction se termine par panic, tous les defers seront appelés

Questions pièges.

Les defer seront-ils exécutés si une panic se produit à l'intérieur de la fonction ?

Oui ! Toutes les fonctions defer seront appelées même en cas de panic, c'est le mécanisme principal de "finalisation".

Quand les arguments de la fonction, passés dans defer, sont-ils évalués ?

Au moment de la déclaration de defer, et non pas quand elle est réellement exécutée. Par conséquent, si des variables sont utilisées qui sont modifiées par la suite, il est important de le garder à l'esprit :

a := 1 defer fmt.Println(a) a = 2 // affichera 1, pas 2

Comment fonctionne defer à l'intérieur d'une boucle ? Cela ne causera-t-il pas une fuite de mémoire ?

Si un defer est utilisé dans chaque itération de la boucle, tous les defer s'exécuteront seulement après la fin de la fonction entière, et non après chaque itération — toute la pile des fonctions defer s'accumulera, ce qui peut entraîner une consommation excessive de mémoire.

for i := 0; i < 3; i++ { defer fmt.Println(i) }

Erreurs typiques et anti-modèles

  • Utilisation de defer dans des boucles, ce qui entraîne une libération différée des ressources (par exemple, des connexions à une base de données)
  • Complète confiance que les variables dans defer ne changent pas - mais leur valeur est fixée immédiatement
  • Libération différée des ressources lourdes trop tard plutôt que d'un appel manuel

Exemple de la vie réelle

Cas négatif

Ouverture de mille fichiers dans une boucle, et pour chacun, un defer est utilisé. Tous les fichiers ne seront fermés qu'à la fin de la fonction entière et les ressources seront retenues, ce qui conduira à une "fuite" — dépasser la limite des fichiers ouverts.

Avantages :

  • Brièveté de l'écriture
  • Garantie de libération dans tous les cas

Inconvénients :

  • Fuite de ressources jusqu'à la fin de la fonction entière
  • Erreurs lors d'opérations massives avec defer

Cas positif

Dans la boucle, des fonctions locales sont utilisées, où defer est appliqué uniquement pour la portée de ce fichier, et non pour l'ensemble du gestionnaire :

for _, name := range fileNames { func() { f, _ := os.Open(name) defer f.Close() // traitement avec f }() }

Avantages :

  • Retour immédiat des ressources
  • Pas de fuites

Inconvénients :

  • Plus difficile à lire (imbrication supplémentaire des fonctions)
  • Nécessité de se souvenir de la portée de defer