Overview
… The Context should be the first parameter, typically named ctx:
func DoSomething(ctx context.Context, arg Arg) error { // ... use ctx ... }
This short sentence includes two “rules”. Let’s talk about both, why they exist, and when to (possibly) violate them.
-
Context should be the first parameter.
Why this is a rule is probably a lot less important than following the rule. But let’s consider the why anyway. This is similar to the way many functions return an
error
. The best place for a common argument/return value is either as the first, or last in a list. (Arguably, Go could have chosen to makeerror
always be the first return value–I won’t discuss that here). But, since variadic variables may only be the last in the list of function arguments, this leaves the only option for a common argument to be the first one.This pattern is often seen with other variables in Go (and other languages) as well. Any time a common variable is used by a set of related functions, that common variable often comes first in the argument list (or possibly second, after
ctx
).When are you “allowed” to violate this rule?
I honestly don’t know if I have ever violated this rule. The only semi-plausible example I can think of where, I would consider violating this rule, is if I needed to pass two (or more!??) contexts to the same function. Obviously, only one can be first.
The revive linter, also included in golangci-lint, has a
context-as-argument
rule, which will warn you when you violate this convention. -
Name your contexts
ctx
.This is a slightly less important rule, in my opinion, but still a good convention to follow. It’s great to know, at a glance, that an argument represents a
context.Context
value. I was recently working on a codebase that instead called all context values simplyc
. This was rather confusing at first. The team and I worked on remaing them all over time, and now the code is that much easier to read.When are you “allowed” to violate this rule? There are a few times I’ve run into:
-
The most obvious: You have more than one context in scope. I usually see when using a context to trigger a service shutdown:
// Wait for context to cancel <-ctx.Done() // Then begin a graceful shutdown: shutdownCtx, cancel := context.WithTimeout(5 * time.Second) defer cancel() server.Shutdown(shutdownCtx)
In such a case, use a descriptive name. I still tend to use
ctx
for the “default” context, and a longer name for the subordinate one.- You’re using another library or framework that also uses
ctx
by convention. I’m not aware of any of these, and if they exist, they probably predate Go 1.7, when thecontext
package was introduced. But they might exist. If you’re using such a library, you’ll have to decide which convention to follow–Go’s convention, or the framework’s.
Certainly, there are many frameworks that use the concept of a “context” that is different from the
context.Context
sense. Virtually any web framework is an example. Arguably, “context” was a bad name for this package, since its meaning depends on, well…. context! -