Alternative to using context.Value

Daisuke Maki
3 min readMar 25, 2017

--

I love context.Context. But I don’t like using context.Value(). Because it relies on using interface{}, it requires the programmers to write a possibly panic-inducing type assertion like this:

func foo(ctx context.Context) {
v := ctx.Value(key).(*MyThing) // eek
...
}

Obviously it’s possible to avoid the panic by using a two return value form of type assertion, but that just adds more code for each function that requires the use of this value.

Let’s step back and think what context.WithValue is supposed to offer us.

I believe that WithValue and Value were created for bundling data to a particular set of processing routines. To that end, you are (MOSTLY) expecting tight coupling of the data associated to that context.Context and the routines.

For example, you cannot expect a function call to modify the registered value of a context.Context object when you pass one to it:

func mutatingFunc(ctx context.Context) {
ctx = context.WithValue(ctx, key, ...)
...
}
ctx := context.WithValue(context.Background(), key, ...)
mutatingFunc(ctx)
ctx.Value(key) // "mutatingFunc" can't influence this value

The only way to change the registered value is to “exchange” the context.Context object:

func mutatingFunc(ctx context.Context) context.Context {
return context.WithValue(ctx, key, ...)
}
ctx = mutatingFunc(ctx) // ctx is now replaced with a new context
ctx.Value(key) // value is what "mutatingFunc" set to it

This means changes are “local” to the scope where the context.Context was created. You can’t interact with the upper scope. So its use is pretty limited.

Instead, I like to just make a wrapper for context.Context, with the correct type and struct field. Here’s an example:

type myCtx struct {
context.Context
value *MyThing
}
func EntryPoint(ctx context.Context) {
myctx := &myCtx{ // Create your own context.Context wrapper
Context: ctx,
value: ... // whatever local value
}
foo(myctx)
}
func foo(ctx *myCtx) { // expect the concret type, not the interface
v := ctx.value // Safe!
...
}

There are two nice properties that this pattern brings.

The first is that we are now using type-safe constructs. No more type assertions.

The second is that myCtx is still completely compatible with context.Context. This property also comes for free, because we’re just using anonymously embedded context.Context. Therefore you can use this object as an argument to functions expecting context.Context without doing anything further. Go’s implicit interface matching FTW.

ctx := &myCtx{}
srv := &http.Server{...}
srv.Shutdown(ctx) // totally legal!

The down side is obviously that you can not create new contexts out of the myctx value and still have the easy access to the context-specific value associated with it, since it will be surrounded by a black-box context.Context object.

newctx, cancel := context.WithCancel(myctx)
// newctx is no longer *myCtx
newctx.value // WRONG!

But then again, your code is probably the only thing that relies on this context-specific field anyway. And since you have control over it, it’s up to you to re-create a new context that inherits the value. Maybe you can write code like this:

// Create a new myCtx with associated cancellation
func (ctx *myCtx) WithCancel() (*myCtx, func()) {
newctx, cancel := context.WithCancel(ctx.Context)
return &myCtx{
Context: newctx,
value: octx.value,
}, cancel
}
newctx, cancel := myctx.WithCancel()
// newctx can be used as *myCtx
newctx.value // SAFE!

This will allow you to inherit the required context-specific fields while using a quasi-context.Context compatible API.

There are legitimate uses for context.WithValue, but in most cases I believe this sort of simple wrapping that matches your particular needs are MUCH safer compared to trying very hard to using vanilla context.Value

Happy hacking!

--

--

Daisuke Maki

Go/perl hacker; author of peco; works @ Mercari; ex-mastermind of builderscon; Proud father of three boys;