2
0

key.go 4.5 KB

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