grpc-goはGoogle製gRPCのGo実装で、Go標準ライブラリ並みに読まれている「お手本コード」です。今回選んだattributes.goは、gRPC内部でコネクションやアドレスに付随情報(キー/バリュー)を持たせるための小さなパッケージ。mapを使わずポインタの連結リストで「不変(イミュータブル)な辞書」を作るという、実務でよく効くイディオムが詰まっているので教材に選びました。

コード

type Attributes struct {
	parent     *Attributes
	key, value any
}

// New returns a new Attributes containing the key/value pair.
func New(key, value any) *Attributes {
	return &Attributes{
		key:   key,
		value: value,
	}
}

// WithValue returns a new Attributes containing the previous keys and values
// and the new key/value pair.  If the same key appears multiple times, the
// last value overwrites all previous values for that key.  value should not be
// modified later.
//
// Note that Attributes do not support deletion. Avoid using untyped nil values.
// Since the Value method returns an untyped nil when a key is absent, it is
// impossible to distinguish between a missing key and a key explicitly set to
// an untyped nil. If you need to represent a value being unset, consider
// storing a specific sentinel type or a wrapper struct with a boolean field
// indicating presence.
func (a *Attributes) WithValue(key, value any) *Attributes {
	return &Attributes{
		parent: a,
		key:    key,
		value:  value,
	}
}

// Value returns the value associated with these attributes for key, or nil if
// no value is associated with key.  The returned value should not be modified.
func (a *Attributes) Value(key any) any {
	for cur := a; cur != nil; cur = cur.parent {
		if cur.key == key {
			return cur.value
		}
	}
	return nil
}

引用はリポジトリの実物と機械で照合しています。「// …略…」は省略した行です。

上から順に読む

type Attributes struct {
	parent     *Attributes
	key, value any
}

構造体`Attributes`の定義です。`key, value any`は「keyとvalueという2つのフィールドを両方とも`any`型で宣言する」というGoの省略記法(同じ型ならカンマ区切りでまとめて書ける)。`any`はGo 1.18以降で`interface{}`の別名として使えるようになった型で、「どんな型の値でも入れられる」という意味です。ポイントは`parent *Attributes`。`*Attributes`は「Attributes型へのポインタ」で、しかも自分自身の型を指しています。つまりこの構造体は1つのkey/valueペアしか持たず、前のペアへのポインタを`parent`として持つ「連結リスト」になっています。mapのように全部のペアを1つの箱にまとめて持つのではなく、鎖のように1個ずつ繋がっている点がこのコードの肝です。

func New(key, value any) *Attributes {
	return &Attributes{
		key:   key,
		value: value,
	}
}

最初の1ペアを作るコンストラクタ関数です。`&Attributes{...}`は「Attributes構造体のリテラル(値)を作って、その先頭に`&`を付けることでポインタにする」という書き方。Goでは`&構造体名{フィールド: 値, ...}`でヒープ上に値を確保しつつポインタを得るのが定番パターンです。この時点では`parent`フィールドを指定していないので、Goのゼロ値ルールにより自動的に`nil`(何も指していない状態)になります。つまりこの`New`で作った`Attributes`は「鎖の一番最初の輪」=親を持たない末端です。

func (a *Attributes) WithValue(key, value any) *Attributes {
	return &Attributes{
		parent: a,
		key:    key,
		value:  value,
	}
}

`(a *Attributes)`は「メソッドレシーバ」と呼ばれる書き方で、この関数を`*Attributes`型の値に対して`a.WithValue(...)`のように呼べるようにします。中身を見ると、既存の`a`をそのまま`parent`に入れた**新しい**`Attributes`を作って返しているだけで、`a`自身のフィールドは一切書き換えていません。つまり「値を追加する」といっても元のデータには触らず、新しい鎖の輪を先頭に付け足して返す設計です。これにより、誰かが古い`Attributes`(`a`)への参照を持っていても、後から別の場所で`WithValue`されて内容が変わることはありません。gRPCのようにコネクションやアドレス情報が複数のゴルーチン(goroutine、Goの軽量スレッド)から同時に参照されうる場面では、「作った後は絶対に変わらない」という性質(イミュータブル)がデータ競合を防ぐ強力な武器になります。

func (a *Attributes) Value(key any) any {
	for cur := a; cur != nil; cur = cur.parent {
		if cur.key == key {
			return cur.value
		}
	}
	return nil
}

指定した`key`に対応する値を探すメソッドです。`for cur := a; cur != nil; cur = cur.parent { ... }`はGoのfor文の三点セット(初期化; 継続条件; 後処理)で、「`cur`を`a`から始めて、`nil`でない間繰り返し、毎回`cur`を`cur.parent`(1つ前の輪)に進める」という連結リストの巡回処理です。各ステップで`cur.key == key`かどうか比べ、一致したらその`cur.value`を返して終了(`return`はループも関数も即座に抜けます)。もし鎖の終わり(`parent`が`nil`)まで見つからなければ`nil`を返します。ここで大事なのは巡回が`a`(=一番新しく追加された輪)から始まる点です。`WithValue`のコメントにある「同じキーが複数回出てきたら最後の値が勝つ」という仕様は、この「新しい方から順に探して最初に見つかったものを返す」というシンプルな探索順序だけで実現されています。

← 2026.09.13 の号を読む