key.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // ctxkey provides type-safe key-value pairs for use with [context.Context].
  4. //
  5. // Example usage:
  6. //
  7. // // Create a context key.
  8. // var TimeoutKey = ctxkey.New("mapreduce.Timeout", 5*time.Second)
  9. //
  10. // // Store a context value.
  11. // ctx = mapreduce.TimeoutKey.WithValue(ctx, 10*time.Second)
  12. //
  13. // // Load a context value.
  14. // timeout := mapreduce.TimeoutKey.Value(ctx)
  15. // ... // use timeout of type time.Duration
  16. //
  17. // This is inspired by https://go.dev/issue/49189.
  18. package ctxkey
  19. import (
  20. "context"
  21. "fmt"
  22. "reflect"
  23. )
  24. // TODO(https://go.dev/issue/60088): Use reflect.TypeFor instead.
  25. func reflectTypeFor[T any]() reflect.Type {
  26. return reflect.TypeOf((*T)(nil)).Elem()
  27. }
  28. // Key is a generic key type associated with a specific value type.
  29. //
  30. // A zero Key is valid where the Value type itself is used as the context key.
  31. // This pattern should only be used with locally declared Go types,
  32. // otherwise different packages risk producing key conflicts.
  33. //
  34. // Example usage:
  35. //
  36. // type peerInfo struct { ... } // peerInfo is a locally declared type
  37. // var peerInfoKey ctxkey.Key[peerInfo]
  38. // ctx = peerInfoKey.WithValue(ctx, info) // store a context value
  39. // info = peerInfoKey.Value(ctx) // load a context value
  40. type Key[Value any] struct {
  41. name *stringer[string]
  42. defVal *Value
  43. }
  44. // New constructs a new context key with an associated value type
  45. // where the default value for an unpopulated value is the provided value.
  46. //
  47. // The provided name is an arbitrary name only used for human debugging.
  48. // As a convention, it is recommended that the name be the dot-delimited
  49. // combination of the package name of the caller with the variable name.
  50. // If the name is not provided, then the name of the Value type is used.
  51. // Every key is unique, even if provided the same name.
  52. //
  53. // Example usage:
  54. //
  55. // package mapreduce
  56. // var NumWorkersKey = ctxkey.New("mapreduce.NumWorkers", runtime.NumCPU())
  57. func New[Value any](name string, defaultValue Value) Key[Value] {
  58. // Allocate a new stringer to ensure that every invocation of New
  59. // creates a universally unique context key even for the same name
  60. // since newly allocated pointers are globally unique within a process.
  61. key := Key[Value]{name: new(stringer[string])}
  62. if name == "" {
  63. name = reflectTypeFor[Value]().String()
  64. }
  65. key.name.v = name
  66. if v := reflect.ValueOf(defaultValue); v.IsValid() && !v.IsZero() {
  67. key.defVal = &defaultValue
  68. }
  69. return key
  70. }
  71. // contextKey returns the context key to use.
  72. func (key Key[Value]) contextKey() any {
  73. if key.name == nil {
  74. // Use the reflect.Type of the Value (implies key not created by New).
  75. return reflectTypeFor[Value]()
  76. } else {
  77. // Use the name pointer directly (implies key created by New).
  78. return key.name
  79. }
  80. }
  81. // WithValue returns a copy of parent in which the value associated with key is val.
  82. //
  83. // It is a type-safe equivalent of [context.WithValue].
  84. func (key Key[Value]) WithValue(parent context.Context, val Value) context.Context {
  85. return context.WithValue(parent, key.contextKey(), stringer[Value]{val})
  86. }
  87. // ValueOk returns the value in the context associated with this key
  88. // and also reports whether it was present.
  89. // If the value is not present, it returns the default value.
  90. func (key Key[Value]) ValueOk(ctx context.Context) (v Value, ok bool) {
  91. vv, ok := ctx.Value(key.contextKey()).(stringer[Value])
  92. if !ok && key.defVal != nil {
  93. vv.v = *key.defVal
  94. }
  95. return vv.v, ok
  96. }
  97. // Value returns the value in the context associated with this key.
  98. // If the value is not present, it returns the default value.
  99. func (key Key[Value]) Value(ctx context.Context) (v Value) {
  100. v, _ = key.ValueOk(ctx)
  101. return v
  102. }
  103. // Has reports whether the context has a value for this key.
  104. func (key Key[Value]) Has(ctx context.Context) (ok bool) {
  105. _, ok = key.ValueOk(ctx)
  106. return ok
  107. }
  108. // String returns the name of the key.
  109. func (key Key[Value]) String() string {
  110. if key.name == nil {
  111. return reflectTypeFor[Value]().String()
  112. }
  113. return key.name.String()
  114. }
  115. // stringer implements [fmt.Stringer] on a generic T.
  116. //
  117. // This assists in debugging such that printing a context prints key and value.
  118. // Note that the [context] package lacks a dependency on [reflect],
  119. // so it cannot print arbitrary values. By implementing [fmt.Stringer],
  120. // we functionally teach a context how to print itself.
  121. //
  122. // Wrapping values within a struct has an added bonus that interface kinds
  123. // are properly handled. Without wrapping, we would be unable to distinguish
  124. // between a nil value that was explicitly set or not.
  125. // However, the presence of a stringer indicates an explicit nil value.
  126. type stringer[T any] struct{ v T }
  127. func (v stringer[T]) String() string { return fmt.Sprint(v.v) }