
1 min read
String context keys
By now we’ve looked at empty structs (struct{}), and integers as possible context key types. Today, let’s consider string context keys. type contextKey string const ( KeyUserID contextKey = "user_id" KeyTransactionID contextKey = "transaction_id" KeyMessageID contextKey = "message_id" ) How does this stack up against our 5 criteria? Must be comparable. ✅ Check. Use minimal memory. ✅ Using a string will typically use a bit more memory than an integer (typically 32 bytes vs 16), but still quite minimal.

2 min read
Integer context keys
We’re looking at different types for context keys. So far, we’ve looked at empty structs (struct{}), and found it to be less than ideal. Today, let’s consider integer context keys. This seems handy, right? type contextKey int const ( KeyUserID int = iota KeyTransactionID KeyMessageID . . . KeyFoo ) Let’s see how it stacks up to our 5 criteria: Must be comparable. ✅ No problem! Use minimal membory. ✅ int doesn’t have as small a memory footprint as struct{}’s zero bytes, but it’s still pretty small.

2 min read
Context key types
So we’ve established that context keys should be of an unexported type in your package. That still leaves a lot of options. Which type is ideal? Today we’ll look at some options, and their pros and cons. Before looking at specific types, let’s consider what sorts of things we want from such a type. It must be comparable, or it won’t work as a key. This means we can’t use function or channel types, for example.

1 min read
Unexported context key types
Last week we saw the potential dangers of context key collisions. Today let’s look at how to avoid this problem. The GoDoc we looked at last week gave us a clue: // packages should define keys as an unexported type to avoid // collisions. Let’s dive into what this is suggesting, and why it works. First: context.WithValue accepts any comparable type as a key. This includes custom types. And by using a custom type that’s unexported, we can ensure that no other package uses our keys.

2 min read
Context value key collisions
Let’s talk about keys for context values. type Context type Context interface { … // A key identifies a specific value in a Context. Functions that wish // to store values in Context typically allocate a key in a global // variable then use that key as the argument to context.WithValue and // Context.Value. A key can be any type that supports equality; // packages should define keys as an unexported type to avoid // collisions.

2 min read
Context values
I’ve already talked a bit about context values, and when not to use them. For a recap, take a look at these previous posts, so I don’t have to rehash those points today: Context abuse Context values and type safety When not to use context values So from here on out, we’re assuming that you have a legitimate reason to store values in a context. How can/should you go about it?

2 min read
Context errors
type Context type Context interface { … // If Done is not yet closed, Err returns nil. // If Done is closed, Err returns a non-nil error explaining why: // DeadlineExceeded if the context's deadline passed, // or Canceled if the context was canceled for some other reason. // After Err returns a non-nil error, successive calls to Err return the same error. Err() error Done() (covered yesterday) and Err() (today) are the two methods you’ll virtually alway use when writing code to honor context cancelations.
Subscribe to Boldly Go: Daily
Every day I'll send you advice to improve your understanding of Go. Don't miss out! I will respect your inbox, and honor my privacy policy.
Unsure? Browse the archive.

5 min read
The Done channel
May I rant for a moment? I don’t like the way GoDoc works for structs and interfaces. And below is a demonstration as to why. It’s not possible to link directly to an interface method or struct field, and the formatting is ugly. sigh type Context type Context interface { … // Done returns a channel that's closed when work done on behalf of this // context should be canceled. Done may return nil if this context can // never be canceled.

2 min read
Context deadlines
On Friday we looked at the context.Context interface as a whole. Now let’s go back and look at each method in greater detail. type Context type Context interface { // Deadline returns the time when work done on behalf of this context // should be canceled. Deadline returns ok==false when no deadline is // set. Successive calls to Deadline return the same results. Deadline() (deadline time.Time, ok bool) So there are two general cases here:

2 min read
The Context API contract
Today we come to the core of the context package: The Context interface itself. type Context type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key any) any } A Context carries a deadline, a cancellation signal, and other values across API boundaries. Context’s methods may be called by multiple goroutines simultaneously. For clarity, I’ve removed all of the documentation for each of the interface methods in the above quote—we’ll get to those in following emails, and include those there.

2 min read
Contexts with timeouts
func WithTimeout func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)). Canceling this context releases resources associated with it, so code should call cancel as soon as the operations running in this Context complete: func slowOperationWithTimeout(ctx context.Context) (Result, error) { ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) defer cancel() // releases resources if slowOperation completes before timeout elapses return slowOperation(ctx) } Not only is WithTimeout conceptually similar to WithDeadline, it literally is WithDeadline, as we can see form the source code: