En Go, des appels différés (defer) sont utilisés conjointement avec des fermetures anonymes pour garantir le nettoyage ou la libération des ressources. Ce modèle permet de regrouper la logique de nettoyage et de gérer correctement les erreurs, assurant ainsi un code lisible et fiable.
Historique de la question :
Le defer est emprunté à d'autres langages et simplifie considérablement la vie des développeurs Go. La combinaison de defer et de fermetures est devenue une norme pour garantir le nettoyage des fichiers, connexions et autres ressources externes dans des blocs où il peut y avoir de nombreuses sorties (return, panic).
Problème :
Avec une logique complexe pour le nettoyage des ressources (comme des fichiers, connexions, emplacements), il est nécessaire de garantir qu'en cas d'erreur ou de sortie de la fonction, le nettoyage se fasse. Une mauvaise utilisation peut entraîner des fuites, un ordre incorrect de nettoyage ou des erreurs sans sens.
Solution :
Utiliser defer avec une fonction anonyme (closure) pour :
Exemple de code :
package main import ( "fmt" "os" ) func WriteFileDemo(filename string) (err error) { f, err := os.Create(filename) if err != nil { return } defer func() { cerr := f.Close() if cerr != nil && err == nil { err = cerr } }() // Logique de travail avec le fichier fmt.Fprintln(f, "Hello world") return // defer sera exécuté même si ici il y a un return } func main() { if err := WriteFileDemo("test.txt"); err != nil { fmt.Println("Erreur:", err) } }
Caractéristiques clés :
Quand les variables utilisées à l'intérieur d'une fermeture différée sont-elles capturées - au moment de la déclaration de defer ou lors de l'appel effectif ?
Elles sont capturées au moment de la déclaration de defer, mais si la fermeture fait référence aux variables par référence, leurs valeurs au moment de l'exécution de defer seront utilisées. Cela peut parfois conduire à des résultats inattendus.
for i := 0; i < 3; i++ { defer func() { fmt.Println(i) }() // imprimera 2, 2, 2 }
Peut-on passer des valeurs à la fermeture par le biais de paramètres pour éviter la capture par référence ?
Oui, il est possible de déclarer des paramètres pour une fonction anonyme et de leur passer les valeurs actuelles - dans ce cas, les valeurs sont « gelées » sous forme de copies.
for i := 0; i < 3; i++ { defer func(n int) { fmt.Println(n) }(i) // imprimera 2, 1, 0 }
Que faire si une panique est détectée dans une fermeture différée ? Comment la gérer ?
À l'intérieur de la fermeture, on utilise la construction recover() pour empêcher la panique de s'échapper et mettre en œuvre une récupération en douceur de la fonctionnalité.
defer func() { if r := recover(); r != nil { log.Println("Récupéré:", r) } }()
Le code ouvre plusieurs fichiers, mais oublie de mettre defer f.Close(). En cas d'erreur, il retourne le contrôle, et une partie des fichiers reste non nettoyée, entraînant une fuite de ressources.
Avantages :
Inconvénients :
Une fermeture différée est utilisée pour toutes les opérations de vente : ferme soigneusement le flux de fichiers et gère l'erreur, même si le fichier n'a pas été complètement écrit.
Avantages :
Inconvénients :