Created
November 18, 2020 14:29
-
-
Save mccutchen/9f1034dc12ef0fd80cac5b48dda44fed to your computer and use it in GitHub Desktop.
Revisions
-
mccutchen created this gist
Nov 18, 2020 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,11 @@ # golang scoping surprise Here's a short snippet of code that demonstrates a surprising effect of golang's variable scoping rules. (This is maybe only surprising for me, with Python's scoping rules deeply ingrained in my brain.) In the real world, this came up in the context of an HTTP middleware function, where state accumulated in surprising ways across multiple requests to the handler wrapped by the middleware. Try it out on the Go Playground: https://play.golang.org/p/s2KHE-8Kbyd This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,59 @@ package main import "fmt" func main() { data := Data{[]string{"main"}} a := wrap(data, "A", func(data Data) { fmt.Printf(" in func A: %#v\n", data) }) b := wrap(data, "B", func(data Data) { fmt.Printf(" in func B: %#v\n", data) }) a() a() a() b() b() // Output: // wrapping func A: main.Data{Value:[]string{"main"}} // wrapping func B: main.Data{Value:[]string{"main"}} // calling func A: main.Data{Value:[]string{"main", "A"}} // in func A: main.Data{Value:[]string{"main", "A"}} // calling func A: main.Data{Value:[]string{"main", "A", "A"}} // in func A: main.Data{Value:[]string{"main", "A", "A"}} // calling func A: main.Data{Value:[]string{"main", "A", "A", "A"}} // in func A: main.Data{Value:[]string{"main", "A", "A", "A"}} // calling func B: main.Data{Value:[]string{"main", "B"}} // in func B: main.Data{Value:[]string{"main", "B"}} // calling func B: main.Data{Value:[]string{"main", "B", "B"}} // in func B: main.Data{Value:[]string{"main", "B", "B"}} } func wrap(data Data, name string, f func(Data)) func() { fmt.Printf("wrapping func %s: %#v\n", name, data) return func() { // This modifies `data` in the outer scope, even though WithValue // returns a new "instance" of Data, so subsequent calls to the wrapped // function will accumulate additional values. // // This caught me off guard, though it probably shouldn't have. // // The fix is to use := instead of = to declare a new variable with the // same name that would shadow `data` in the outer scope (or just use a // different name altogether). data = data.WithValue(name) fmt.Printf(" calling func %s: %#v\n", name, data) f(data) } } type Data struct { Value []string } func (d Data) WithValue(val string) Data { return Data{append(d.Value, val)} }