Globally unique key for context value in Golang

Michał Łowicki
golangspec
2 min readSep 17, 2017

--

Suppose you’ve a context which is passed across many packages. Any of these packages can put there information (f.ex. something related to request being handled). You want to make sure keys won’t clash. If package A puts something under key K then package B cannot overwrite value under that key.

Let’s gather some facts:

  • Context values are internally stored as empty interface (interface{}) values (source code)
  • Equality operator is used to check if context stores value for desired key (source code)
  • Language specification defines when two interface values are equal:

Two interface values are equal if they have identical dynamic types and equal dynamic values or if both have value nil.

Based on above if we’ll put key of dynamic type specific to package setting that key then we’ll be fine. This way dynamic type won’t be identical to dynamic type set by any other package. Equation from Value method will be always false. For that we’ll simply use type which isn’t exported from package using it. We’ve two packages — a and b:

package mainimport (
"context"
"fmt"
"github.com/mlowicki/b"
)
func main() {
ctx, _ := context.WithCancel(context.Background())
ctx = b.Set(ctx)
fmt.Println(b.Get(ctx))
fmt.Println(ctx.Value(1))
}
package bimport (
"context"
)
type key intvar id = key(1)func Set(ctx context.Context) context.Context {
return context.WithValue(ctx, id, "secret")
}
func Get(ctx context.Context) (string, bool) {
val, ok := ctx.Value(id).(string)
return val, ok
}

output:

secret true
<nil>

Value stored under key(1) is safe since only code in package b can use type b.key.

The same mechanism won’t work with maps since it’s possible to iterate over their elements and retrieve keys and values.

Edit

Discussion on Reddit brought some ideas:

  • const can be used instead of var to not allow to change key’s value
  • type key struct{} can be used and rules above will still hold. Tests with testing.AllocsPerRun show the same number of allocations in Go 1.9 as with using int. Probably this approach is more memory-efficient in some older versions of Go.

Credits to TheMerovius and epiris.

--

--

Michał Łowicki
golangspec

Software engineer at Datadog, previously at Facebook and Opera, never satisfied.