Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 17482] ファイルの概要

このコミットは、Go言語の reflect パッケージにおける内部定数 BUCKETSIZE, MAXKEYSIZE, MAXVALSIZE を非公開(unexported)にする変更です。これらの定数は、Goのマップ型(map)の内部実装に関連するもので、リフレクションを通じてマップの型情報を扱う際に使用されていました。

コミット

commit da50221e8eaa2d68bf003f3417dae2e73fdc8b2b
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Fri Sep 6 12:00:42 2013 -0700

    reflect: unexport BUCKETSIZE, MAXKEYSIZE, MAXVALSIZE
    
    But keep their case for ease of searching.
    
    They were added recently. We don't want them part of go1.2's API.
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/13569044

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/da50221e8eaa2d68bf003f3417dae2e73fdc8b2b

元コミット内容

reflect: unexport BUCKETSIZE, MAXKEYSIZE, MAXVALSIZE But keep their case for ease of searching. They were added recently. We don't want them part of go1.2's API.

変更の背景

このコミットの主な背景は、Go言語の「Go 1 互換性保証(Go 1 Compatibility Promise)」にあります。Go 1リリース以降、Goチームは言語仕様や標準ライブラリのAPIについて厳格な互換性保証を行っています。これは、Go 1でリリースされたAPIは、将来のGoのバージョンでも変更されないことを意味します。

BUCKETSIZE, MAXKEYSIZE, MAXVALSIZE といった定数は、Go 1.2のリリースに向けて reflect パッケージに最近追加されたものでした。これらはマップの内部実装に関する詳細であり、Goのユーザーが直接利用することを意図したものではありませんでした。もしこれらの定数が公開されたAPIの一部として残ってしまうと、将来的にマップの内部実装が変更された場合に、互換性保証を破ることなくこれらの定数を変更することが困難になります。

そのため、Goチームはこれらの定数を非公開(unexported)にすることで、Go 1.2の公式APIセットから除外し、将来的な内部実装の変更の自由度を確保することを決定しました。コミットメッセージにある「But keep their case for ease of searching.」は、非公開にする際に慣例的に小文字で始まる名前に変更するところを、検索のしやすさのために大文字のまま残したことを示唆しています。これは、内部コードベースでの検索性を維持しつつ、外部からのアクセスを制限するための配慮です。

前提知識の解説

Go言語のリフレクション (reflect パッケージ)

Go言語の reflect パッケージは、実行時にプログラムの構造を検査・操作するための機能を提供します。これにより、変数、関数、構造体などの型情報を動的に取得したり、値の読み書きを行ったりすることが可能になります。例えば、reflect.TypeOf(i) は任意のインターフェース値 i の動的な型を返し、reflect.ValueOf(i) はその値の動的な表現を返します。

リフレクションは、汎用的なデータシリアライゼーション(JSONエンコーディング/デコーディングなど)、ORM(Object-Relational Mapping)、テストフレームワーク、DI(Dependency Injection)コンテナなど、多くの高度なプログラミングパターンで利用されます。しかし、リフレクションは実行時のオーバーヘッドが大きく、型安全性を損なう可能性があるため、必要不可欠な場合にのみ使用することが推奨されます。

Go言語の公開(Exported)と非公開(Unexported)識別子

Go言語では、識別子(変数名、関数名、型名など)の最初の文字が大文字であるか小文字であるかによって、その識別子がパッケージの外部からアクセス可能(公開、Exported)であるか、パッケージ内部からのみアクセス可能(非公開、Unexported)であるかが決まります。

  • 公開(Exported)識別子: 最初の文字が大文字で始まる識別子(例: Name, Func, Type)。これらは、その識別子が定義されているパッケージをインポートした他のパッケージからアクセスできます。GoのAPIとして提供されるものは、通常、公開識別子です。
  • 非公開(Unexported)識別子: 最初の文字が小文字で始まる識別子(例: name, func, _type)。これらは、その識別子が定義されているパッケージ内からのみアクセスできます。パッケージの内部実装の詳細であり、外部に公開すべきではないものに用いられます。

このコミットでは、既存の公開識別子であった BUCKETSIZE などを非公開にするために、アンダースコア _ をプレフィックスとして追加し、_BUCKETSIZE のように変更しています。これにより、Goの慣例に従い、これらの定数がパッケージ外部から参照できなくなります。

Go 1 互換性保証 (Go 1 Compatibility Promise)

Go 1 互換性保証は、Go言語の重要な設計原則の一つです。Go 1.0がリリースされた2012年3月以降、GoチームはGo 1で導入された言語仕様と標準ライブラリのAPIについて、将来のGoのバージョンでも互換性を維持することを約束しています。これは、Go 1で書かれたプログラムが、新しいGoのバージョンでも再コンパイルするだけで動作することを保証するものです。

この保証は、Goエコシステムの安定性と成長に大きく貢献しています。開発者は、Goのバージョンアップによって既存のコードが壊れる心配をすることなく、安心してGoを使用し、ライブラリを構築できます。しかし、この保証は同時に、一度公開されたAPIは変更が非常に困難になるという制約も生み出します。そのため、Goチームは新しいAPIを公開する際に非常に慎重であり、内部実装の詳細が誤ってAPIとして公開されないように細心の注意を払っています。

技術的詳細

このコミットは、Goの reflect パッケージ内の type.go ファイルに定義されていた3つの定数 BUCKETSIZE, MAXKEYSIZE, MAXVALSIZE を非公開化するものです。これらの定数は、Goのマップ型(map[K]V)の内部構造、特にメモリレイアウトとガベージコレクション(GC)プログラムの生成に関連していました。

Goのマップはハッシュテーブルとして実装されており、内部的には「バケット(bucket)」と呼ばれるデータ構造にキーと値のペアが格納されます。

  • BUCKETSIZE: 1つのバケットに格納できるキーと値のペアの最大数を示します。
  • MAXKEYSIZE: マップのキーとして許容される最大サイズ(バイト単位)を示します。これを超えるサイズのキーは、ポインタとして扱われる可能性があります。
  • MAXVALSIZE: マップの値として許容される最大サイズ(バイト単位)を示します。これを超えるサイズの値は、ポインタとして扱われる可能性があります。

これらの定数は、reflect パッケージが MapOf 関数を通じて新しいマップ型を動的に生成する際に、そのマップのメモリレイアウトやGCプログラムを決定するために内部的に使用されていました。具体的には、bucketOf 関数内でキーや値のサイズが MAXKEYSIZEMAXVALSIZE を超えるかどうかに応じて、ポインタとして扱うかどうかの判断が行われ、またGCプログラムの生成において BUCKETSIZE が利用されていました。

コミットの変更は、これらの定数の名前の先頭にアンダースコア _ を追加することで、Goの言語仕様に従ってこれらを非公開識別子に変更しています。これにより、これらの定数は reflect パッケージの内部でのみ参照可能となり、外部のパッケージからはアクセスできなくなります。

この変更の技術的な影響は以下の通りです。

  1. APIの安定性: これらの定数が非公開になったことで、Goチームは将来的にマップの内部実装(例えば、バケットのサイズやキー/値の最大サイズに関する制約)を変更する際に、Go 1互換性保証を破ることなく、これらの定数の値を自由に調整できるようになります。これは、Goランタイムの最適化や改善において重要な柔軟性を提供します。
  2. カプセル化の強化: マップの内部実装の詳細が reflect パッケージの外部に漏れることを防ぎ、より良いカプセル化を実現します。これにより、ユーザーはマップの内部構造に依存するコードを書くことがなくなり、Goのバージョンアップによる予期せぬ動作変更のリスクが低減されます。
  3. 検索性の維持: コミットメッセージにある通り、定数の名前を _BUCKETSIZE のように変更しても、元の BUCKETSIZE という文字列が部分的に残るため、Goのソースコードベース内でこれらの定数に関連するコードを検索する際の利便性が維持されます。これは、開発者が内部コードを理解し、デバッグする上で役立ちます。

コアとなるコードの変更箇所

変更は src/pkg/reflect/type.go ファイルの1箇所に集中しています。

--- a/src/pkg/reflect/type.go
+++ b/src/pkg/reflect/type.go
@@ -1539,40 +1539,40 @@ func MapOf(key, elem Type) Type {
 // Currently, that's just size and the GC program.  We also fill in string
 // for possible debugging use.
 const (
-	BUCKETSIZE = 8
-	MAXKEYSIZE = 128
-	MAXVALSIZE = 128
+	_BUCKETSIZE = 8
+	_MAXKEYSIZE = 128
+	_MAXVALSIZE = 128
 )
 
 func bucketOf(ktyp, etyp *rtype) *rtype {
-	if ktyp.size > MAXKEYSIZE {
+	if ktyp.size > _MAXKEYSIZE {
 		ktyp = PtrTo(ktyp).(*rtype)
 	}
-	if etyp.size > MAXVALSIZE {
+	if etyp.size > _MAXVALSIZE {
 		etyp = PtrTo(etyp).(*rtype)
 	}
 	ptrsize := unsafe.Sizeof(uintptr(0))
 
 	gc := make([]uintptr, 1)                                       // first entry is size, filled in at the end
-	offset := BUCKETSIZE * unsafe.Sizeof(uint8(0))                 // topbits
+	offset := _BUCKETSIZE * unsafe.Sizeof(uint8(0))                // topbits
 	gc = append(gc, _GC_PTR, offset, 0 /*self pointer set below*/) // overflow
 	offset += ptrsize
 
 	// keys
 	if ktyp.kind&kindNoPointers == 0 {
-		gc = append(gc, _GC_ARRAY_START, offset, BUCKETSIZE, ktyp.size)
+		gc = append(gc, _GC_ARRAY_START, offset, _BUCKETSIZE, ktyp.size)
 		gc = appendGCProgram(gc, ktyp)
 		gc = append(gc, _GC_ARRAY_NEXT)
 	}
-	offset += BUCKETSIZE * ktyp.size
+	offset += _BUCKETSIZE * ktyp.size
 
 	// values
 	if etyp.kind&kindNoPointers == 0 {
-		gc = append(gc, _GC_ARRAY_START, offset, BUCKETSIZE, etyp.size)
+		gc = append(gc, _GC_ARRAY_START, offset, _BUCKETSIZE, etyp.size)
 		gc = appendGCProgram(gc, etyp)
 		gc = append(gc, _GC_ARRAY_NEXT)
 	}
-	offset += BUCKETSIZE * etyp.size
+	offset += _BUCKETSIZE * etyp.size
 
 	gc = append(gc, _GC_END)
 	gc[0] = offset

コアとなるコードの解説

このコミットは、src/pkg/reflect/type.go ファイル内の以下の変更を含んでいます。

  1. 定数宣言の変更:

    const (
    -	BUCKETSIZE = 8
    -	MAXKEYSIZE = 128
    -	MAXVALSIZE = 128
    +	_BUCKETSIZE = 8
    +	_MAXKEYSIZE = 128
    +	_MAXVALSIZE = 128
    )
    

    ここで、BUCKETSIZE, MAXKEYSIZE, MAXVALSIZE という公開されていた定数名が、それぞれ _BUCKETSIZE, _MAXKEYSIZE, _MAXVALSIZE と、先頭にアンダースコア _ が付加された非公開名に変更されています。これにより、これらの定数は reflect パッケージの外部からは参照できなくなります。

  2. 定数使用箇所の変更: bucketOf 関数内で、上記で名前が変更された定数が使用されている箇所がすべて新しい非公開名に更新されています。

    • ktyp.size > MAXKEYSIZEktyp.size > _MAXKEYSIZE に変更。
    • etyp.size > MAXVALSIZEetyp.size > _MAXVALSIZE に変更。
    • offset := BUCKETSIZE * unsafe.Sizeof(uint8(0))offset := _BUCKETSIZE * unsafe.Sizeof(uint8(0)) に変更。
    • gc = append(gc, _GC_ARRAY_START, offset, BUCKETSIZE, ktyp.size)gc = append(gc, _GC_ARRAY_START, offset, _BUCKETSIZE, ktyp.size) に変更。
    • offset += BUCKETSIZE * ktyp.sizeoffset += _BUCKETSIZE * ktyp.size に変更。
    • gc = append(gc, _GC_ARRAY_START, offset, BUCKETSIZE, etyp.size)gc = append(gc, _GC_ARRAY_START, offset, _BUCKETSIZE, etyp.size) に変更。
    • offset += BUCKETSIZE * etyp.sizeoffset += _BUCKETSIZE * etyp.size に変更。

これらの変更は、定数の可視性を変更するだけであり、定数の値や bucketOf 関数のロジック自体には影響を与えません。これにより、reflect パッケージの内部動作は維持されつつ、外部からの依存が排除されます。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード (golang/go GitHubリポジトリ)
  • Go言語の公式ドキュメント
  • Go 1 Compatibility Promiseに関する情報
  • Go言語における公開/非公開識別子のルールに関する情報