Globally unique key for context value in Golang
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 ofvar
to not allow to change key’s valuetype 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 usingint
. Probably this approach is more memory-efficient in some older versions of Go.
Credits to TheMerovius and epiris.
Clap 👏 to help others discover this story. Please follow me if you want to get updates about new posts or boost work on future stories.