Skip to content

Instantly share code, notes, and snippets.

@mccutchen
Created November 18, 2020 14:29
Show Gist options
  • Save mccutchen/9f1034dc12ef0fd80cac5b48dda44fed to your computer and use it in GitHub Desktop.
Save mccutchen/9f1034dc12ef0fd80cac5b48dda44fed to your computer and use it in GitHub Desktop.
golang scoping surprise

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

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)}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment