Tags: #go #ctx
https://pkg.go.dev/tailscale.com/util/ctxkey
Example Playground: https://play.golang.com/p/aZ0joNec3Xl
package main
import (
"context"
"fmt"
"time"
"tailscale.com/util/ctxkey"
)
var TimeoutKey = ctxkey.New("mapreduce.Timeout", 5*time.Second)
func main() {
ctx := context.Background()
fmt.Println(TimeoutKey.Value(ctx))
// Have to overwrite the ctx with the returned value.
// Otherwise the default value will still be associated with ctx.
ctx = TimeoutKey.WithValue(ctx, 10*time.Second)
fmt.Println(TimeoutKey.Value(ctx))
}
ctxkey
over standard Go context
?The core difference lies in type safety.
Standard context
Package (context.WithValue
, ctx.Value
)
context.WithValue(parentCtx, key, value)
. The key
is typically an unexported custom type (like type myKey struct{}
) to prevent collisions. You retrieve the value using val := ctx.Value(key)
.ctx.Value(key)
returns a value of type interface{}
. This means you must perform a type assertion to get the value back in its original type: realVal, ok := val.(ExpectedType)
.ok
boolean), your program might panic or behave unexpectedly only when that specific code path is executed. There’s no compile-time guarantee that the value associated with a key is of the type you expect. This can lead to subtle bugs that are harder to catch during development.tailscale.com/util/ctxkey
uniqueCtxKey = ctxkey.New(""unique-key-name"", uint32(1))
(and you can assign a DEFAULT value, 1 in this case).uniqueCtxKey.WithValue(ctx, 2)
.uniqueCtxKey.Value(ctx)
.Value
function returns the specific type associated with the key (uint32
in the example above). The Go compiler checks this at compile time.tailscale.com/util/ctxkey
?val.(ExpectedType)
type assertion when retrieving values.ctxkey.NewKey[ValueType]
explicitly states the type of value the key is intended for.context
?context
package is part of the Go standard library. Using ctxkey
introduces a dependency on tailscale.com/util/ctxkey
.context
package.You would want to use tailscale.com/util/ctxkey
primarily when you want stronger, compile-time guarantees about the types of values stored in your context. This is particularly beneficial in larger projects or teams where maintaining type consistency across different parts of the codebase is crucial for preventing runtime errors and improving maintainability. The trade-off is adding an external dependency.