Context keys in Go

Mat Ryer
2 min readJul 24, 2016

--

Passing state around via context.Context got a lot easier with Go 1.7 as the context package became part of the standard library.

There seems to be a pattern emerging around the keys to use when setting state.

Inside your package, create a new unexported type called contextKey based on a string:

type contextKey stringfunc (c contextKey) String() string {
return "mypackage context key " + string(c)
}

The various String() methods in https://tip.golang.org/src/context/context.go make you realise that printing a context will reveal a fair amount of information about it. So adding your own String() method is a nice way to keep track of what’s what, although as Sameer pointed out, it would work without it.

You’ll only need one of these types per package, and it doesn’t need to be exported because we’ll instead add helper methods later to get at the values themselves. We should probably try to avoid every public package being polluted with its own package.ContextKey types if we can?

Now we can create our keys as normal (also internal) variables:

var (
contextKeyAuthtoken = contextKey("auth-token")
contextKeyAnother = contextKey("another")
)

Provided no two strings (within the same package) are the same, you can be sure that no two context keys can ever collide (even if the underlying names are the same in different packages) which is pretty important when you consider potentially lots of different components could be using the same context for different (or similar) things.

We can add context data with our new key using context.WithValue:

ctx := context.WithValue(ctx, contextKeyAuthtoken, tok)

If users of our package need to be able to access the object (ideally context would be used internally inside a package and users wouldn’t get exposed to it at all) they can do so through a helper function that will get the value using the same key:

// AuthToken gets the auth token from the context.
func AuthToken(ctx context.Context) (string, bool) {
tokenStr, ok := ctx.Value(contextKeyAuthtoken).(string)
return tokenStr, ok
}

The value might be missing or of the wrong type, so you should type assert it immediately after getting it.

Better still, you could use the context value yourself to do something more useful, keeping all notion of auth token keys completely private:

// User gets the current user for the specified context or
// returns ErrNoUser if there is no user.
func User(ctx context.Context) (*User, error) {
tok, ok := authTokenFromContext(ctx)
if !ok {
return nil, ErrNoUser
}
user, err := LookupUserByToken(ctx, tok)
if err != nil {
return nil, err
}
return user, nil
}

What next?

Read more about context with:

Or watch Sameer Ajmani’s GothamGo 2014 talk on the subject:

EDIT: Type assertion code simplified, no need to check for nil before type asserting to .(string) — it will return false as second argument safely. Thanks @oilerian (via reddit).

--

--

Mat Ryer

Founder at MachineBox.io — Gopher, developer, speaker, author — BitBar app https://getbitbar.com — Author of Go Programming Blueprints