Context Values — Specification¶
This document is the formal description of the context.WithValue constructor and the Context.Value(key) lookup method. It captures every invariant that the standard library guarantees and every constraint that implementers (custom Context types, frameworks, instrumentation libraries) must respect.
1. The Value Method¶
1.1 Signature¶
keyis typed asany(interface{}).- The return type is
any. The caller is responsible for type-asserting the result.
1.2 Lookup Rules¶
Value(key)returns the value most recently associated withkeyin the chain of contexts walked from the receiver toward the root.- If no ancestor associated
keywith a value,Value(key)returnsnil. - If two ancestors associated the same
keywith different values, the closer (more derived) ancestor wins. - Lookup must be deterministic: given the same chain and the same key, every call returns the same value.
- Lookup must be safe for concurrent use from multiple goroutines.
1.3 Performance¶
- Lookup time is proportional to the depth of the chain between the receiver and the ancestor that first set the key (or the root, if the key is not present).
- No standard implementation performs hash lookup; the implementation is a linked walk.
Valuemust not allocate on lookup in the standard library.
2. The WithValue Constructor¶
2.1 Pre-conditions¶
parentmust be non-nil. Calling withparent == nilpanics.keymust be non-nil. Calling withkey == nilpanics.key's dynamic type must be comparable per the Go specification's definition of comparable. Calling with a non-comparable key panics.
2.2 Post-conditions¶
- The returned context, call it
child: child.Value(key)returnsval.child.Value(k)for anyk != key(using==) delegates toparent.Value(k).child.Done()returns the same channel asparent.Done().child.Err()returns the same error asparent.Err().child.Deadline()returns the same deadline asparent.Deadline().WithValuedoes not create a cancellation point. It carries values only.
2.3 Mutability¶
- The stored
valis held by reference (asinterface{}). If the underlying value is mutable, mutations are visible to every goroutine that retrieves the value. The package makes no guarantees about ordering or atomicity of those mutations. - Recommended practice: store values that are either immutable, copy-on-write, or themselves internally synchronized.
3. Key Design Rules¶
3.1 Comparability¶
The Go specification defines comparable types. WithValue accepts any of the following key types:
| Type category | Allowed? | Notes |
|---|---|---|
| Booleans, numerics, strings | Yes | Allowed but discouraged because of collision risk. |
| Pointers | Yes | Address equality. Useful when each package owns its key. |
| Channels | Yes | Identity equality. |
| Interfaces | Yes | Comparison uses both dynamic type and dynamic value. |
| Structs of comparable fields | Yes | Component-wise equality. |
| Arrays of comparable elements | Yes | Element-wise equality. |
| Slices, maps, function values | No | Panic on WithValue. |
| Structs containing non-comparable fields | No | Panic. |
3.2 Idiomatic Key Type¶
The idiomatic key is a private (unexported) named type, typically an empty struct or a typed int:
or
Both forms guarantee: - Uniqueness across packages (no string collision). - Type safety in accessors (you cannot pass an int literal by mistake). - Zero per-key allocation (the key itself is the zero value of the type).
3.3 Forbidden Idioms¶
The following are explicitly anti-idiomatic. They are not enforced by the runtime, but go vet's contextkeys analyzer and staticcheck's SA1029 will flag them:
context.WithValue(ctx, "user", u)— string keys.context.WithValue(ctx, 42, u)— bare int keys.- Using a built-in type from another package as a key.
4. Value Lifetime¶
- A value placed in a context is reachable as long as any goroutine holds a reference to that context (or any descendant).
- The runtime does not provide a mechanism to remove a value once added; the only way to make it unreachable is to drop all references to the entire chain.
- Storing large values (megabytes of data, open file handles, channels) is discouraged because it extends their lifetime to that of the longest-lived descendant context.
5. Concurrency¶
Valuemay be called concurrently from any number of goroutines.WithValueis not itself a synchronization point; it returns a new context immediately. Any goroutine receiving the returned context observes the value (subject to the standard Go memory model when the context reference is communicated).- Mutating a value retrieved from
Valueis the caller's responsibility to synchronize.
6. Interaction with Cancellation¶
WithValuereturns a context that delegatesDone,Err, andDeadlineto its parent. It does not introduce a new cancellation point.context.WithoutCancel(parent)(Go 1.21+) preserves values while severing cancellation.Valuelookups on the result still walk the original chain.
7. Reserved Behaviour¶
context.Background()andcontext.TODO()returnnilfromValue(any).- Calling
Valueon a canceled context returns the same values as before cancellation. Cancellation does not erase values. - The runtime does not deduplicate keys; calling
WithValuetwice with the same key produces a two-link chain, and the outer link shadows the inner.
8. Standard Library Usage¶
The following standard library packages place values into the request context. Their key types are unexported, so the values are accessible only via the package's own accessor functions:
| Package | Key concept | Accessor |
|---|---|---|
net/http | Original *Server, local addr, TLS conn | http.ServerContextKey, http.LocalAddrContextKey (exported) |
net/http/httptrace | Active *ClientTrace | httptrace.ContextClientTrace(ctx) |
runtime/pprof | Pprof labels | pprof.Labels, pprof.Do |
Note: http.ServerContextKey and http.LocalAddrContextKey are exported keys with a private underlying type — a deliberate exception so handlers can read server-level data.
9. Memory Model¶
- A write to
valviaWithValuehappens-before any subsequent successful retrieval viaValue(key)on the same context or any descendant, provided the context reference is communicated through one of the synchronization primitives defined by the Go memory model. - After
Valuereturns, the caller can use the result without further synchronization, provided the underlying object is either immutable or itself synchronized.
10. Compatibility¶
- The
Value(key any) anysignature is fixed by theContextinterface and will not change. - Future Go versions may introduce additional helpers (similar to
WithoutCancel) that pass values through; they will not change the contract above.
11. Anti-Patterns (Normative)¶
From the package documentation:
- "Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions."
- "The provided key must be comparable and should not be of type string or any other built-in type to avoid collisions between packages using context."
- "Users of WithValue should define their own types for keys."
These are normative requirements; tooling enforces them.
12. Implementation Sketch (Informative)¶
The standard library's valueCtx is roughly:
type valueCtx struct {
Context
key, val any
}
func (c *valueCtx) Value(key any) any {
if c.key == key {
return c.val
}
return value(c.Context, key)
}
Lookup is a linear walk up the parent chain. There is no map, no hash, and no goroutine-local storage. This is by design: it makes the cost predictable, the lifetime explicit, and the API resistant to misuse as a global cache.
13. Interaction With Other Constructors¶
13.1 With WithCancel¶
context.WithCancel(parent) returns a *cancelCtx whose Value(k) delegates to parent.Value(k). A value placed on parent is visible from the returned cancelable child. A value placed on the cancelable child (via WithValue(child, k, v)) is not visible on parent.
13.2 With WithDeadline and WithTimeout¶
Same as 13.1. Values pass through transparently.
13.3 With WithCancelCause (Go 1.20+)¶
Same as 13.1. The cause mechanism does not interact with the value mechanism.
13.4 With WithoutCancel (Go 1.21+)¶
WithoutCancel(parent) preserves Value delegation to parent. The returned context inherits the entire value chain. Only cancellation is severed.
13.5 With AfterFunc (Go 1.21+)¶
AfterFunc(ctx, f) registers f for invocation on cancel. The function f is called in a new goroutine without any context argument. If f needs context values, capture them explicitly in the closure before calling AfterFunc.
14. Order of Evaluation in WithValue¶
expensiveCall() is evaluated in the calling goroutine before WithValue returns. There is no lazy storage. The value is captured by reference (through the any interface boxing) at the moment of the call.
15. Failure Modes¶
Failures fall into three categories:
| Failure | When | Effect |
|---|---|---|
parent == nil | Caller bug | Panic with "cannot create context from nil parent" |
key == nil | Caller bug | Panic with "nil key" |
| Non-comparable key | Caller bug | Panic with "key is not comparable" |
| Type assertion failure on read | Caller bug (wrong key or wrong type at write site) | Either runtime panic (if .(T) form) or ok == false (if comma-ok form) |
| Missing value | Misconfiguration | Value returns nil |
All five failure modes are caller-side, not runtime-induced. The standard library does not produce errors in this API.
16. Stability and Version History¶
| Go version | Change |
|---|---|
| 1.7 | context moved into the standard library from golang.org/x/net/context. WithValue API as documented. |
| 1.20 | WithCancelCause, Cause — no change to value semantics. |
| 1.21 | WithoutCancel, AfterFunc, WithDeadlineCause, WithTimeoutCause — value delegation rules extended to WithoutCancel. |
| 1.24 | (*testing.T).Context() — convenience for tests; no change to WithValue semantics. |
The signature func WithValue(parent Context, key, val any) Context has not changed since Go 1.7.
17. References¶
- Go source:
src/context/context.go, typevalueCtx. - Package documentation: https://pkg.go.dev/context#WithValue
- Proposal: "Go 1.7 context package" — design note on linear lookup.
- Go specification, section "Comparison operators" — defines comparable types.
go vetanalyzers:contextkeys,lostcancel.staticcheck:SA1029(use of built-in type as context key).- Effective Go: notes on context first-parameter convention.
- Go memory model: https://go.dev/ref/mem
- OpenTelemetry Go specification — describes how trace context is carried through
context.Contextvalues.