【Go】contextパッケージでkeyの重複を回避する方法
はじめに
Go言語のcontextパッケージを使用する際に、値を共有するために使われるValue型のkeyは、interface{}型であることが特徴的です。
context.WithValue()関数
context.WithValue()関数は、キーと値のペアをcontextに設定するために使用されます。この関数は、以下のように定義されています。
func WithValue(parent Context, key, val interface{}) Context
keyには、どのような型でも指定できます。一方、valには、interface{}型を満たす任意の型の値を指定することができます。
context.WithValue には以下のコメントがあります。
// WithValue returns a copy of parent in which the value associated with key is
// val.
//
// 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. To avoid allocating when assigning to an
// interface{}, context keys often have concrete type
// struct{}. Alternatively, exported context key variables' static
// type should be a pointer or interface.
keyの衝突
contextパッケージのValue型のkeyは、値を共有するために使われますが、異なるパッケージ間で同じ名前のkeyを使用すると、値が上書きされてしまうことがあります。
例えば、以下のようにpkg1パッケージとpkg2パッケージで同じ名前のkeyを使用した場合、値が上書きされてしまいます。
package pkg1
import "context"
func Foo(ctx context.Context) {
ctx = context.WithValue(ctx, "key", "value1")
}
package pkg2
import "context"
func Bar(ctx context.Context) {
ctx = context.WithValue(ctx, "key", "value2")
}
上記のように、異なるパッケージで同じ名前のkeyを使用すると、value2で上書きされてしまいます。このような問題を避けるためには、各パッケージで独自のkeyを使用することが重要です。
解決策1:非公開key型を定義する
非公開のkey型を定義することで、各パッケージで独自のkeyを使用することができます。以下は、pkg1パッケージとpkg2パッケージで異なる型のkeyを使用する例です。
package pkg1
import "context"
type key int
const (
a key = iota
)
func Foo(ctx context.Context) {
ctx = context.WithValue(ctx, a, "value1")
}
package pkg2
import "context"
type key int
const (
a key = iota
)
func Bar(ctx context.Context) {
ctx = context.WithValue(ctx, a, "value2")
}
上記のように、pkg1, pkg2パッケージ共にkey型という非公開型を定義し、それぞれkey型の定数aをkeyとしてcontextに値を付与しています。
解決策2:構造体型のゼロ値を使う
構造体型のゼロ値を使うことで、各パッケージで独自のkeyを使用することができます。以下は、pkg1パッケージとpkg2パッケージで異なる構造体型を使用する例です。
package pkg1
import "context"
type key1 struct{}
func Foo(ctx context.Context) {
ctx = context.WithValue(ctx, key1{}, "value1")
}
package pkg2
import "context"
type key2 struct{}
func Bar(ctx context.Context) {
ctx = context.WithValue(ctx, key2{}, "value2")
}
上記のように、key1とkey2という異なる構造体型を定義し、各パッケージで独自のkeyを使用することができます。
まとめ
Go言語のcontextパッケージを使用する際に、Value型のkeyには、どのような型でも指定できますが、異なるパッケージで同じ名前のkeyを使用すると、値が上書きされることがあります。この問題を回避するために、各パッケージで独自のkeyを使用する方法として、非公開key型を定義する方法と、構造体型のゼロ値を使う方法があります。どちらの方法でも、各パッケージで異なるkeyを使用することができます。適切にkeyを定義することで、contextパッケージを効果的に利用することができます。
Discussion