Un defer vit en deux temps : ses arguments sont évalués maintenant, son appel s'exécute à la sortie.A defer lives in two times: its arguments are evaluated now, its call runs at exit.
Le numéro précédent a posé le quand de defer : à la sortie de la fonction, quel que soit le chemin. Ce numéro précise le quoi — qu'est-ce, exactement, qui est mis de côté ? Et la réponse est plus subtile qu'il n'y paraît, parce que defer fonctionne en deux temps distincts. Quand l'exécution rencontre la ligne defer, deux choses se produisent à des instants différents. Premièrement, sur-le-champ, Go évalue les arguments de l'appel différé. Si tu écris defer log(x), la valeur de x est lue et figée à cet instant précis, même si l'appel à log n'aura lieu que bien plus tard. Deuxièmement, à la sortie de la fonction, Go exécute enfin l'appel, avec les arguments qu'il avait gelés. Cet écart — arguments évalués tôt, appel exécuté tard — est invisible tant que les variables concernées ne changent pas entre les deux moments. Mais dès qu'elles changent, le comportement surprend : le defer utilise la photographie prise à l'instant du defer, pas l'état final. C'est une décision de conception délibérée, et plutôt sensée : elle capture l'intention du moment où tu écris le defer. Mais elle a une exception majeure, qui est la source de presque tous les pièges du numéro : si l'appel différé est une fermeture sans arguments, alors il n'y a rien à évaluer tôt, et le corps de la fermeture lira les variables à la sortie, dans leur état final. Appel direct avec arguments, ou fermeture : ces deux formes capturent à deux moments opposés. Tout le numéro vit dans cet écart.The previous issue laid down defer's when: at the function's exit, whatever the path. This issue clarifies the what — what, exactly, is set aside? And the answer is subtler than it seems, because defer works in two distinct times. When execution meets the defer line, two things happen at different instants. First, on the spot, Go evaluates the deferred call's arguments. If you write defer log(x), the value of x is read and frozen at that precise instant, even though the call to log will only happen much later. Second, at the function's exit, Go finally runs the call, with the arguments it had frozen. This gap — arguments evaluated early, call run late — is invisible as long as the variables involved don't change between the two moments. But the moment they change, the behavior surprises: the defer uses the photograph taken at the instant of defer, not the final state. It's a deliberate design decision, and a rather sensible one: it captures the intent of the moment you write the defer. But it has a major exception, the source of nearly all the issue's traps: if the deferred call is a closure with no arguments, then there's nothing to evaluate early, and the closure's body will read the variables at exit, in their final state. Direct call with arguments, or closure: these two forms capture at two opposite moments. The whole issue lives in that gap.
func demo() { x := 1 defer fmt.Println("différé :", x) // x est ÉVALUÉ ICI → fige la valeur 1 x = 2 fmt.Println("immédiat :", x) // 2 } // sortie : // immédiat : 2 // différé : 1 ← l'argument a été figé au moment du defer, pas à la sortie
Entre les deux instants, la fonction continue et les variables changent. Un appel différé defer f(x) a déjà figé x ; une fermeture différée, elle, lira x à la sortie. Savoir laquelle tu écris, c'est savoir ce que tu vas obtenir.Between the two instants, the function continues and variables change. A deferred call defer f(x) has already frozen x; a deferred closure will read x at exit. Knowing which you write is knowing what you'll get.
« Mon defer a-t-il des arguments, ou est-ce une fermeture ? Le premier fige maintenant, la seconde lit à la sortie. » Cette seule question dissipe la quasi-totalité des surprises. Tu choisis le moment de capture en choisissant la forme : passe une valeur en argument pour la figer tout de suite, mets-la dans le corps d'une fermeture pour la lire à la fin. Les deux sont utiles — encore faut-il savoir laquelle on a sous les doigts."Does my defer have arguments, or is it a closure? The first freezes now, the second reads at exit." This single question dispels nearly all the surprises. You choose the moment of capture by choosing the form: pass a value as an argument to freeze it at once, put it in a closure's body to read it at the end. Both are useful — you just need to know which one you have under your fingers.
Le choix de Go — évaluer les arguments à l'instant du defer — n'est pas arbitraire. Il capture l'état du monde au moment où tu décides de différer, ce qui est souvent exactement ce qu'on veut. Un defer trace(time.Now()) en début de fonction fige l'heure de début, pas l'heure de fin : précisément l'usage attendu. Un defer log(currentStep) enregistre l'étape où l'on était quand on a posé le defer. En gelant les arguments, Go te permet de capturer un instantané sans avoir à le stocker toi-même dans une variable temporaire. La fermeture offre le comportement inverse — lire l'état final — pour les cas où c'est ce qu'on veut. Deux outils, deux moments de capture, et la responsabilité de choisir. Le langage ne devine pas ton intention ; il te donne deux formes claires et te laisse l'exprimer.Go's choice — evaluating arguments at the instant of defer — isn't arbitrary. It captures the state of the world at the moment you decide to defer, which is often exactly what you want. A defer trace(time.Now()) at a function's start freezes the start time, not the end time: precisely the expected use. A defer log(currentStep) records the step you were at when you placed the defer. By freezing the arguments, Go lets you capture a snapshot without having to store it yourself in a temporary variable. The closure offers the opposite behavior — read the final state — for cases where that's what you want. Two tools, two moments of capture, and the responsibility to choose. The language doesn't guess your intent; it gives you two clear forms and lets you express it.