En Go, le mot-clé defer retarde l'exécution d'une fonction jusqu'à la fin de la fonction englobante. C'est pratique pour libérer des ressources et finaliser. Cependant, l'utilisation de defer en combinaison avec des méthodes prenant des pointeurs (*T) ou des valeurs (T) présente des nuances subtiles.
Pour des raisons de sécurité du code, Go a été conçu à l'origine pour garantir l'appel automatique du code de nettoyage, quel que soit le lieu de sortie de la fonction. Cependant, le type de receveur utilisé (pointeur ou valeur) détermine si l'objet de la méthode qui appelle defer est copié ou si l'objet original est modifié.
Lorsque nous appelons une méthode avec un receveur de valeur (T), la valeur de la structure est copiée dans defer. Si nous appelons une méthode avec un receveur pointeur (*T), la méthode travaille avec l'objet original. Par conséquent, les modifications des données dans la méthode defer par valeur ne seront pas visibles, tandis qu'à l'aide d'un pointeur, elles se refléteront sur la structure externe. Cela entraîne des erreurs difficiles à détecter, surtout lorsque l'on essaie de modifier l'état d'un objet via defer.
Lors de la conception de méthodes et de l'utilisation de defer, il convient de toujours choisir consciemment le type de receveur. Les modifications qui doivent subsister jusqu'à la fin de la fonction doivent être effectuées via un pointeur.
Exemple de code :
package main import "fmt" type Counter struct { Value int } func (c Counter) IncValue() { // méthode par valeur defer func() { c.Value++ // seulement la copie augmentera fmt.Println("[Valeur du receveur defer] Valeur :", c.Value) }() } func (c *Counter) IncPointer() { // méthode par pointeur defer func() { c.Value++ // l'original augmentera fmt.Println("[Pointeur du receveur defer] Valeur :", c.Value) }() } func main() { c := Counter{Value: 10} c.IncValue() // La valeur restera 10 c.IncPointer() // La valeur deviendra 11 fmt.Println("Valeur originale après les appels :", c.Value) }
Points clés :
1. L'objet externe changera-t-il si nous appelons dans defer une méthode prenant un receveur de valeur ?
Non, dans defer, l'objet original ne changera pas, car la méthode travaille avec une copie de la structure.
2. Peut-on compter sur defer pour garantir un changement d'état de la structure via une méthode de valeur ?
Non. Il faut utiliser des méthodes avec un pointeur si vous voulez refléter les changements sur l'objet original.
3. Que se passera-t-il si nous modifions les champs de la structure après la déclaration de defer ?
Les actions dépendent de la manière dont le receveur est passé : si la méthode/fonction reçoit une copie, les modifications après la déclaration de defer ne seront pas visibles dans la fonction différée.
func (c Counter) Demo() { defer fmt.Println("defer c.Value :", c.Value) // capturera la valeur actuelle c.Value = 42 // n'affectera pas defer }
Nous avions écrit une fonction pour fermer une connexion, mettant à jour l'état via defer avec une méthode de valeur. Il s'est avéré que le drapeau de fermeture ne se mettait pas à jour.
Avantages :
Inconvénients :
Nous avons utilisé des méthodes avec un receveur pointeur pour la finalisation, modifiant l'objet original.
Avantages :
Inconvénients :