Introduction : Pourquoi « simple struct » n'est pas si simple ?
\n\nAvez-vous déjà écrit en Go et, en regardant votre programme, vous êtes demandé : « Où vit ma structure actuellement ? Sur la stack rapide et confortable ou dans les vastes étendues du heap sous la surveillance du garbage collector ? ». Cette question tourmente non seulement les débutants. Sur Stack Overflow, le sujet « Stack vs heap allocation of structs in Go, and how they relate to garbage collection » a recueilli des milliers de vues. Et ce n'est pas un hasard.
\n\nLa simplicité apparente de Go cache un mécanisme puissant — l'escape analysis. C'est un détective du compilateur qui décide du sort de chaque objet. Sa décision impacte les performances de votre application et la charge sur le GC (Garbage Collector). Dans ce tutoriel pas à pas, nous allons enfiler le chapeau de détective et comprendre :
\n- \n
- Pourquoi une structure vit sur la stack et une autre dans le heap ? \n
- Comment les pointeurs et les closures brisent l'idylle de la stack. \n
- Comment cette connaissance vous aidera à écrire du code Go rapide et efficace. \n
Stack vs Heap : Petit rappel théorique
\nAvant de plonger dans le code, rafraîchissons-nous la théorie. Ne vous inquiétez pas, ce sera rapide et indolore.
\n\nStack
\n- \n
- Vitesse : Éclair. L'allocation mémoire est simplement un déplacement du pointeur vers le bas. \n
- Cycle de vie : Strictement lié à l'appel de fonction. Vit tant que la fonction s'exécute. \n
- Gestion : Automatique. « Brûle » à la sortie de la fonction. \n
- Taille : Petite et fixe par goroutine (généralement 2-4 Ko). \n
Heap
\n- \n
- Vitesse : Plus lent. Nécessite la recherche d'un bloc mémoire libre. \n
- Cycle de vie : Tant que l'objet est référencé par le programme. Peut survivre à la fonction qui l'a créé. \n
- Gestion : Garbage collector (GC). Il scanne le heap, trouve les objets « morts » et libère la mémoire. Cela coûte des ressources CPU. \n
- Taille : Grande (limitée uniquement par la mémoire du système d'exploitation). \n
La règle d'or de Go : « Gardez les données sur la stack aussi longtemps que possible ». Cela réduit la charge sur le GC et accélère le programme.
\n\nEscape Analysis : Le détective en chef de Go
\nEn Go, vous n'écrivez pas new ou malloc pour indiquer explicitement où placer un objet. Le compilateur décide lui-même pour vous. Ce processus s'appelle l'escape analysis (analyse d'évasion).
Il examine votre code et pose une question : « La référence à cet objet peut-elle quitter la fonction actuelle ? ».
\n- \n
- Si NON (l'objet ne s'échappe pas) → L'objet est placé sur la stack. \n
- Si OUI (l'objet s'échappe) → L'objet est placé dans le heap. \n
Voyons cela en action.
\n\nExemple 1 : Structure sécurisée sur la stack
\npackage main\n\nimport "fmt"\n\ntype Point struct {\n X, Y int\n}\n\n// createPoint crée un Point et le retourne par valeur.\nfunc createPoint(x, y int) Point {\n p := Point{X: x, Y: y}\n return p // p est copié lors du retour\n}\n\nfunc main() {\n p1 := createPoint(10, 20)\n fmt.Println(p1)\n}\n\nDans cet exemple, p est une variable locale. Elle est créée dans createPoint et retournée par valeur (copiée). La structure p elle-même ne peut pas être utilisée en dehors de la fonction (sa copie est utilisée dans main). L'escape analysis le voit et place joyeusement p sur la stack. Le GC se repose.
Exemple 2 : Évasion via un pointeur
\nMaintenant, modifions une seule ligne — retournons un pointeur.
\npackage main\n\nimport "fmt"\n\ntype Point struct {\n X, Y int\n}\n\n// createPointer crée un Point et retourne un pointeur vers lui.\nfunc createPointer(x, y int) *Point {\n p := P