[インデックス 1119] ファイルの概要
このコミットは、Go言語のreflect
パッケージにおける配列(特に実行時配列、現在のGoにおけるスライスに相当)の操作能力を拡張し、既存のAPIの命名規則を改善することを目的としています。具体的には、以下のファイルが変更されました。
src/lib/reflect/cast_amd64.s
: AMD64アーキテクチャ向けのアセンブリコードで、ポインタとアドレス間の変換関数が追加されました。src/lib/reflect/gencast.sh
:reflect
パッケージのキャスト関数を生成するためのスクリプトで、新しい型が追加されました。src/lib/reflect/test.go
:reflect
パッケージのテストファイルで、APIの変更と新しい機能のテストが追加されました。src/lib/reflect/value.go
:reflect
パッケージの主要な型とインターフェースが定義されているファイルで、配列操作に関する機能拡張とAPIの命名変更が行われました。
コミット
commit e4f4ab0b8d2e221909cbf15c6867d4990f09e5a8
Author: Russ Cox <rsc@golang.org>
Date: Thu Nov 13 13:42:59 2008 -0800
more array methods
R=r
OCL=19172
CL=19172
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e4f4ab0b8d2e221909cbf15c6867d4990f09e5a8
元コミット内容
このコミットの目的は、「より多くの配列メソッド」を追加することです。これは、Go言語のreflect
パッケージが、実行時に配列(特に動的な配列、現在のGoにおけるスライス)をより柔軟に操作できるようにするための機能拡張を指しています。
変更の背景
Go言語の初期段階において、reflect
パッケージはプログラムの実行時に型情報を検査し、値を操作するための基本的な機能を提供していました。しかし、動的な配列(スライス)のような複雑なデータ構造を効率的かつ安全に操作するためには、さらなる機能が必要とされていました。
このコミットは、特に以下の点に対処するために行われました。
- スライス(当時の「実行時配列」)の完全なリフレクションサポート: Goのスライスは、内部的にデータへのポインタ、長さ、容量という3つの要素で構成されています。
reflect
パッケージがこれらの内部構造にアクセスし、操作できるようにすることで、より高度な汎用コードの記述が可能になります。 - APIの一貫性向上:
reflect
パッケージ内の値設定メソッドの命名がPut
からSet
に変更されました。これは、Go言語全体のAPI設計における命名規則の統一と一貫性向上の一環であり、より直感的で予測可能なAPIを提供することを目的としています。 - 動的な配列の生成: 実行時に新しいスライスを生成し、その長さや容量を設定できる機能は、特にシリアライゼーション/デシリアライゼーション、データ変換、または汎用的なデータ構造を扱うライブラリにおいて不可欠です。
これらの変更は、Go言語がより成熟し、強力なリフレクション機能を持つための重要なステップでした。
前提知識の解説
Go言語のreflect
パッケージ
reflect
パッケージは、Goプログラムが自身の構造(型、値、関数など)を検査し、実行時に操作するための機能を提供します。これにより、以下のようなことが可能になります。
- 型の検査: 変数の具体的な型を、コンパイル時ではなく実行時に知ることができます。
- 値の操作: 変数の値を読み取ったり、変更したりすることができます。
- 構造体のフィールドへのアクセス: 構造体のフィールド名やタグを読み取り、その値にアクセスできます。
- 関数の呼び出し: 実行時に任意の関数を呼び出すことができます。
reflect
パッケージは、主に汎用的なライブラリ(例:JSONエンコーダ/デコーダ、ORM、テストフレームワーク)を記述する際に使用されますが、その強力さゆえに乱用するとパフォーマンスの低下やコードの複雑化を招く可能性があるため、慎重な使用が求められます。
Go言語の配列とスライス
- 配列 (Array): Goの配列は、固定長で同じ型の要素のシーケンスです。配列の長さは型の一部であり、コンパイル時に決定されます。例:
[5]int
は5つの整数を格納する配列です。 - スライス (Slice): スライスは、Goにおいて最も頻繁に使用される動的な配列です。スライスは配列への参照であり、長さ(
len
)と容量(cap
)を持ちます。長さはスライスに含まれる要素の数、容量は基になる配列が保持できる要素の最大数を示します。スライスは、内部的にはデータへのポインタ、長さ、容量の3つのフィールドを持つ構造体として表現されます。このコミットで言及されている「実行時配列 (RuntimeArray)」は、このスライスの内部表現を指していると考えられます。
アセンブリ言語 (.s
ファイル)
Go言語の標準ライブラリやランタイムの一部は、パフォーマンスが重要な部分や、Go言語自体では直接アクセスできない低レベルな操作(例:メモリ管理、システムコール、特定のCPU命令の利用)を行うために、アセンブリ言語で記述されています。src/lib/reflect/cast_amd64.s
は、AMD64アーキテクチャ上でreflect
パッケージが型変換やポインタ操作を行うための低レベルなルーチンを提供します。
Addr
型とAddrToPtrX
関数
reflect
パッケージでは、メモリ上のアドレスを抽象的に表現するためにAddr
型が使用されます。AddrToPtrX
のような関数は、この抽象的なアドレスを特定のGoの型へのポインタ(例: *int
, *string
)に変換するために使用されます。これは、Goの型システムを迂回して、生のメモリを操作するためのメカニズムの一部です。
技術的詳細
このコミットは、主にreflect
パッケージがGoのスライス(当時の「実行時配列」)をより深く理解し、操作できるようにするための変更と、APIの命名規則の統一を含んでいます。
-
RuntimeArray
構造体の導入:src/lib/reflect/value.go
にRuntimeArray
という新しい構造体が定義されました。これは、Goのスライスが内部的に持つデータポインタ、長さ、容量を表現するためのものです。type RuntimeArray struct { data Addr; // 実際のデータへのポインタ len uint32; // 要素の数 cap uint32; // 割り当てられた要素の最大数 }
この構造体は、
reflect
パッケージがスライスの内部表現に直接アクセスし、操作するための橋渡しとなります。 -
アセンブリ関数による
RuntimeArray
の変換:src/lib/reflect/cast_amd64.s
に、Addr
型と*RuntimeArray
型の間で変換を行う新しいアセンブリ関数が追加されました。reflect·AddrToPtrRuntimeArray
:Addr
を*RuntimeArray
に変換します。reflect·PtrRuntimeArrayToAddr
:*RuntimeArray
をAddr
に変換します。 これらの関数は、Goの型システムを介さずに、低レベルでポインタの型変換を行うために使用されます。
-
gencast.sh
の更新:src/lib/reflect/gencast.sh
スクリプトにRuntimeArray
が追加されました。これにより、reflect
パッケージが提供する様々な型変換関数が、RuntimeArray
型に対しても自動的に生成されるようになります。 -
Value
インターフェースのPut
からSet
へのリネーム:src/lib/reflect/value.go
において、IntValue
,UintValue
,FloatValue
,StringValue
,BoolValue
などの各種Value
インターフェースに定義されていた値設定メソッドの名前が、Put
からSet
に変更されました。 例:IntValue
インターフェースのPut(int)
がSet(int)
に。StringValue
インターフェースのPut(string)
がSet(string)
に。 これは、Go言語のAPI設計における一貫性を高めるための変更であり、より直感的な命名規則を採用しています。
-
ArrayValue
インターフェースとOpenArrayValueStruct
の機能拡張:ArrayValue
インターフェースにCap() int
メソッドが追加され、スライスの容量を取得できるようになりました。OpenArrayValueStruct
(動的な配列、スライスを表現する内部構造体)にCap()
メソッドの実装と、SetLen(len int)
メソッドが追加されました。SetLen
はスライスの長さを変更する機能を提供しますが、容量を超える長さへの設定はパニックを引き起こします。OpenArrayValueStruct
の内部で、実際のデータへのポインタと長さ、容量を保持するために*RuntimeArray
フィールドが導入されました。これにより、スライスの内部構造を直接操作できるようになります。
-
NewOpenArrayValue
関数の追加:src/lib/reflect/value.go
にNewOpenArrayValue(typ ArrayType, len, cap int) ArrayValue
という新しいエクスポート関数が追加されました。この関数は、指定された要素型、初期長さ、容量を持つ新しいスライス(ArrayValue
として)を動的に作成することを可能にします。これは、実行時にスライスを生成する必要がある場合に非常に有用です。 -
テストの更新:
src/lib/reflect/test.go
では、Put
からSet
へのメソッド名変更に対応するテストの修正が行われました。また、新しく追加されたNewOpenArrayValue
関数を使用してスライスを生成し、その要素にアクセス・設定するテストケースが追加され、新しいリフレクション機能が正しく動作することを確認しています。
これらの変更により、Goのreflect
パッケージは、スライスに対するより強力で柔軟な操作能力を獲得し、Go言語の汎用プログラミングの可能性を広げました。
コアとなるコードの変更箇所
src/lib/reflect/cast_amd64.s
@@ -171,3 +171,13 @@ TEXT reflect·PtrBoolToAddr(SB),7,$-8
MOVQ AX, 16(SP)
RET
+TEXT reflect·AddrToPtrRuntimeArray(SB),7,$-8
+ MOVQ 8(SP), AX
+ MOVQ AX, 16(SP)
+ RET
+
+TEXT reflect·PtrRuntimeArrayToAddr(SB),7,$-8
+ MOVQ 8(SP), AX
+ MOVQ AX, 16(SP)
+ RET
Addr
と*RuntimeArray
間の変換を行うアセンブリ関数が追加されています。
src/lib/reflect/value.go
@@ -14,6 +14,7 @@ import (
type Addr uint64 // TODO: where are ptrint/intptr etc?
// Conversion functions, implemented in assembler
+type RuntimeArray struct
func AddrToPtrAddr(Addr) *Addr
func AddrToPtrInt(Addr) *int
func AddrToPtrInt8(Addr) *int8
@@ -33,6 +34,8 @@ func AddrToPtrFloat64(Addr) *float64
func AddrToPtrFloat80(Addr) *float80
func AddrToPtrString(Addr) *string
func AddrToPtrBool(Addr) *bool
+func AddrToPtrRuntimeArray(Addr) *RuntimeArray
+func PtrRuntimeArrayToAddr(*RuntimeArray) Addr
export type Empty interface {} // TODO(r): Delete when no longer needed?
@@ -92,7 +95,7 @@ func MissingCreator(typ Type, addr Addr) Value {
export type IntValue interface {
Kind() int;
Get() int;
- Put(int);
+ Set(int);
Type() Type;
}
@@ -108,7 +111,7 @@ func (v *IntValueStruct) Get() int {
return *AddrToPtrInt(v.addr)
}
-func (v *IntValueStruct) Put(i int) {
+func (v *IntValueStruct) Set(i int) {
*AddrToPtrInt(v.addr) = i
}
// ... (他のValueインターフェースのPutからSetへの変更が続く)
@@ -533,13 +536,9 @@ export type ArrayValue interface {
Type() Type;
Open() bool;
Len() int;
+\tCap() int;
Elem(i int) Value;
-}\n-\n-type OpenArrayValueStruct struct {
-\tCommon;
-\telemtype Type;\n-\telemsize int;\n+\tSetLen(len int);\
}
/*
@@ -547,20 +546,43 @@ type OpenArrayValueStruct struct {
struct Array {
byte* array; // actual data
uint32 nel; // number of elements
+\t\t\tuint32 cap;\
};
*/
+type RuntimeArray struct {
+ data Addr;
+ len uint32;
+ cap uint32;
+}
+
+type OpenArrayValueStruct struct {
+ Common;
+ elemtype Type;
+ elemsize int;
+ array *RuntimeArray;
+}
func (v *OpenArrayValueStruct) Open() bool {
return true
}
func (v *OpenArrayValueStruct) Len() int {
- return int(*AddrToPtrInt32(v.addr+8));
+ return int(v.array.len);
+}
+
+func (v *OpenArrayValueStruct) Cap() int {
+ return int(v.array.cap);
+}
+
+func (v *OpenArrayValueStruct) SetLen(len int) {
+ if len > v.Cap() {
+ panicln("reflect: OpenArrayValueStruct.SetLen", len, v.Cap());
+ }
+ v.array.len = uint32(len);
}
func (v *OpenArrayValueStruct) Elem(i int) Value {
- base := *AddrToPtrAddr(v.addr);
- return NewValueAddr(v.elemtype, base + Addr(i * v.elemsize));
+ return NewValueAddr(v.elemtype, v.array.data + Addr(i * v.elemsize));
}
type FixedArrayValueStruct struct {
@@ -578,6 +600,13 @@ func (v *FixedArrayValueStruct) Len() int {
return v.len
}
+func (v *FixedArrayValueStruct) Cap() int {
+ return v.len
+}
+
+func (v *FixedArrayValueStruct) SetLen(len int) {
+}
+
func (v *FixedArrayValueStruct) Elem(i int) Value {
return NewValueAddr(v.elemtype, v.addr + Addr(i * v.elemsize));
return nil
@@ -592,6 +621,32 @@ func ArrayCreator(typ Type, addr Addr) Value {
\tv.typ = typ;
\tv.elemtype = arraytype.Elem();
\tv.elemsize = v.elemtype.Size();
+\t\tv.array = AddrToPtrRuntimeArray(addr);\
\treturn v;
}
\tv := new(FixedArrayValueStruct);
@@ -768,6 +798,32 @@ export func NewInitValue(typ Type) Value {
return NewValueAddr(typ, PtrUint8ToAddr(&data[0]));
}
+/*
+ Run-time representation of open arrays looks like this:
+ struct Array {
+ byte* array; // actual data
+ uint32 nel; // number of elements
+ uint32 cap; // allocated number of elements
+ };
+*/
+export func NewOpenArrayValue(typ ArrayType, len, cap int) ArrayValue {
+ if !typ.Open() {
+ return nil
+ }
+
+ array := new(RuntimeArray);
+ size := typ.Elem().Size() * cap;
+ if size == 0 {
+ size = 1;
+ }
+ data := new([]uint8, size);
+ array.data = PtrUint8ToAddr(&data[0]);
+ array.len = uint32(len);
+ array.cap = uint32(cap);
+
+ return NewValueAddr(typ, PtrRuntimeArrayToAddr(array));
+}
+
export func NewValue(e Empty) Value {
value, typestring := sys.reflect(e);
p, ok := typecache[typestring];
RuntimeArray
構造体の定義、AddrToPtrRuntimeArray
とPtrRuntimeArrayToAddr
の宣言、Put
からSet
へのメソッド名変更、ArrayValue
インターフェースへのCap()
とSetLen()
の追加、OpenArrayValueStruct
の内部構造の変更、そしてNewOpenArrayValue
関数の追加が行われています。
コアとなるコードの解説
RuntimeArray
構造体とアセンブリ関数
RuntimeArray
構造体は、Goのスライスが内部的にどのように表現されているかをreflect
パッケージが理解するための鍵となります。data
はスライスの基盤となる配列の先頭へのポインタ、len
は現在の要素数、cap
は割り当てられたメモリの最大容量を示します。
AddrToPtrRuntimeArray
とPtrRuntimeArrayToAddr
というアセンブリ関数は、Goの型システムをバイパスして、生のメモリアドレス(Addr
)とRuntimeArray
構造体へのポインタ(*RuntimeArray
)の間で安全に変換を行うために存在します。これにより、reflect
パッケージはスライスの内部表現に直接アクセスし、その長さや容量、基盤となるデータポインタを読み書きできるようになります。これは、Goの型安全性を維持しつつ、低レベルな操作を可能にするための重要なメカニズムです。
Put
からSet
へのメソッド名変更
これは、reflect
パッケージのAPI全体における命名規則の統一を目的とした変更です。Put
という名前は「値を置く」というニュアンスを持ちますが、Set
はより明確に「値を設定する」という動作を示します。この変更により、reflect
パッケージのAPIがより一貫性のあるものとなり、開発者にとって理解しやすくなりました。
ArrayValue
インターフェースとOpenArrayValueStruct
の拡張
ArrayValue
インターフェースにCap()
メソッドが追加されたことで、reflect
パッケージを通じてスライスの容量をプログラム的に取得できるようになりました。これは、スライスのメモリ割り当て状況を把握したり、容量に基づいた最適化を行ったりする際に役立ちます。
OpenArrayValueStruct
は、動的な配列(スライス)のreflect.Value
表現を内部的に管理する構造体です。この構造体が*RuntimeArray
フィールドを持つように変更されたことで、reflect
パッケージはスライスの内部的な長さ、容量、データポインタに直接アクセスできるようになりました。
SetLen(len int)
メソッドの追加は、reflect
パッケージを通じてスライスの長さを動的に変更する機能を提供します。ただし、この操作はスライスの容量を超える長さを設定しようとするとパニックを引き起こすため、安全な使用が求められます。
NewOpenArrayValue
関数
この関数は、reflect
パッケージの最も重要な追加機能の一つです。これにより、開発者は実行時に新しいスライスを動的に作成し、その初期長さと容量を指定できるようになります。これは、例えば、JSONやデータベースから読み込んだデータに基づいて、適切なサイズのスライスを動的に構築するようなシナリオで非常に強力な機能となります。この関数は、Goの型システムがコンパイル時に型を決定するのに対し、実行時に柔軟なデータ構造を生成する能力をreflect
パッケージに与えます。
これらの変更は、Go言語がその初期段階において、リフレクション機能をより堅牢で実用的なものにするための基盤を築いたことを示しています。特にスライスのようなGoのコアデータ構造に対する深いリフレクションサポートは、Goエコシステムにおける多くの汎用ライブラリの発展に貢献しました。
関連リンク
- Go言語の
reflect
パッケージ公式ドキュメント: https://pkg.go.dev/reflect - Go言語のスライスに関する公式ブログ記事 (より現代的な視点): https://go.dev/blog/slices-intro
参考にした情報源リンク
- GitHubコミットページ: https://github.com/golang/go/commit/e4f4ab0b8d2e221909cbf15c6867d4990f09e5a8
- Go言語の初期の設計に関する議論やドキュメント (当時の文脈を理解するため): Go言語の公式リポジトリの歴史的なコミットログや、Goの設計に関する初期のメーリングリストのアーカイブなどが参考になりますが、特定のURLは提供できません。
- Go言語のアセンブリに関するドキュメント: https://go.dev/doc/asm
- Go言語の内部構造に関する情報 (スライスの内部表現など): Goのソースコード自体が最も正確な情報源ですが、Goの内部構造を解説するブログ記事や書籍も参考になります。```markdown
[インデックス 1119] ファイルの概要
このコミットは、Go言語のreflect
パッケージにおける配列(特に実行時配列、現在のGoにおけるスライスに相当)の操作能力を拡張し、既存のAPIの命名規則を改善することを目的としています。具体的には、以下のファイルが変更されました。
src/lib/reflect/cast_amd64.s
: AMD64アーキテクチャ向けのアセンブリコードで、ポインタとアドレス間の変換関数が追加されました。src/lib/reflect/gencast.sh
:reflect
パッケージのキャスト関数を生成するためのスクリプトで、新しい型が追加されました。src/lib/reflect/test.go
:reflect
パッケージのテストファイルで、APIの変更と新しい機能のテストが追加されました。src/lib/reflect/value.go
:reflect
パッケージの主要な型とインターフェースが定義されているファイルで、配列操作に関する機能拡張とAPIの命名変更が行われました。
コミット
commit e4f4ab0b8d2e221909cbf15c6867d4990f09e5a8
Author: Russ Cox <rsc@golang.org>
Date: Thu Nov 13 13:42:59 2008 -0800
more array methods
R=r
OCL=19172
CL=19172
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e4f4ab0b8d2e221909cbf15c6867d4990f09e5a8
元コミット内容
このコミットの目的は、「より多くの配列メソッド」を追加することです。これは、Go言語のreflect
パッケージが、実行時に配列(特に動的な配列、現在のGoにおけるスライス)をより柔軟に操作できるようにするための機能拡張を指しています。
変更の背景
Go言語の初期段階において、reflect
パッケージはプログラムの実行時に型情報を検査し、値を操作するための基本的な機能を提供していました。しかし、動的な配列(スライス)のような複雑なデータ構造を効率的かつ安全に操作するためには、さらなる機能が必要とされていました。
このコミットは、特に以下の点に対処するために行われました。
- スライス(当時の「実行時配列」)の完全なリフレクションサポート: Goのスライスは、内部的にデータへのポインタ、長さ、容量という3つの要素で構成されています。
reflect
パッケージがこれらの内部構造にアクセスし、操作できるようにすることで、より高度な汎用コードの記述が可能になります。 - APIの一貫性向上:
reflect
パッケージ内の値設定メソッドの命名がPut
からSet
に変更されました。これは、Go言語全体のAPI設計における命名規則の統一と一貫性向上の一環であり、より直感的で予測可能なAPIを提供することを目的としています。 - 動的な配列の生成: 実行時に新しいスライスを生成し、その長さや容量を設定できる機能は、特にシリアライゼーション/デシリアライゼーション、データ変換、または汎用的なデータ構造を扱うライブラリにおいて不可欠です。
これらの変更は、Go言語がより成熟し、強力なリフレクション機能を持つための重要なステップでした。
前提知識の解説
Go言語のreflect
パッケージ
reflect
パッケージは、Goプログラムが自身の構造(型、値、関数など)を検査し、実行時に操作するための機能を提供します。これにより、以下のようなことが可能になります。
- 型の検査: 変数の具体的な型を、コンパイル時ではなく実行時に知ることができます。
- 値の操作: 変数の値を読み取ったり、変更したりすることができます。
- 構造体のフィールドへのアクセス: 構造体のフィールド名やタグを読み取り、その値にアクセスできます。
- 関数の呼び出し: 実行時に任意の関数を呼び出すことができます。
reflect
パッケージは、主に汎用的なライブラリ(例:JSONエンコーダ/デコーダ、ORM、テストフレームワーク)を記述する際に使用されますが、その強力さゆえに乱用するとパフォーマンスの低下やコードの複雑化を招く可能性があるため、慎重な使用が求められます。
Go言語の配列とスライス
- 配列 (Array): Goの配列は、固定長で同じ型の要素のシーケンスです。配列の長さは型の一部であり、コンパイル時に決定されます。例:
[5]int
は5つの整数を格納する配列です。 - スライス (Slice): スライスは、Goにおいて最も頻繁に使用される動的な配列です。スライスは配列への参照であり、長さ(
len
)と容量(cap
)を持ちます。長さはスライスに含まれる要素の数、容量は基になる配列が保持できる要素の最大数を示します。スライスは、内部的にはデータへのポインタ、長さ、容量の3つのフィールドを持つ構造体として表現されます。このコミットで言及されている「実行時配列 (RuntimeArray)」は、このスライスの内部表現を指していると考えられます。
アセンブリ言語 (.s
ファイル)
Go言語の標準ライブラリやランタイムの一部は、パフォーマンスが重要な部分や、Go言語自体では直接アクセスできない低レベルな操作(例:メモリ管理、システムコール、特定のCPU命令の利用)を行うために、アセンブリ言語で記述されています。src/lib/reflect/cast_amd64.s
は、AMD64アーキテクチャ上でreflect
パッケージが型変換やポインタ操作を行うための低レベルなルーチンを提供します。
Addr
型とAddrToPtrX
関数
reflect
パッケージでは、メモリ上のアドレスを抽象的に表現するためにAddr
型が使用されます。AddrToPtrX
のような関数は、この抽象的なアドレスを特定のGoの型へのポインタ(例: *int
, *string
)に変換するために使用されます。これは、Goの型システムを迂回して、生のメモリを操作するためのメカニズムの一部です。
技術的詳細
このコミットは、主にreflect
パッケージがGoのスライス(当時の「実行時配列」)をより深く理解し、操作できるようにするための変更と、APIの命名規則の統一を含んでいます。
-
RuntimeArray
構造体の導入:src/lib/reflect/value.go
にRuntimeArray
という新しい構造体が定義されました。これは、Goのスライスが内部的に持つデータポインタ、長さ、容量を表現するためのものです。type RuntimeArray struct { data Addr; // 実際のデータへのポインタ len uint32; // 要素の数 cap uint32; // 割り当てられた要素の最大数 }
この構造体は、
reflect
パッケージがスライスの内部表現に直接アクセスし、操作するための橋渡しとなります。 -
アセンブリ関数による
RuntimeArray
の変換:src/lib/reflect/cast_amd64.s
に、Addr
型と*RuntimeArray
型の間で変換を行う新しいアセンブリ関数が追加されました。reflect·AddrToPtrRuntimeArray
:Addr
を*RuntimeArray
に変換します。reflect·PtrRuntimeArrayToAddr
:*RuntimeArray
をAddr
に変換します。 これらの関数は、Goの型システムを介さずに、低レベルでポインタの型変換を行うために使用されます。
-
gencast.sh
の更新:src/lib/reflect/gencast.sh
スクリプトにRuntimeArray
が追加されました。これにより、reflect
パッケージが提供する様々な型変換関数が、RuntimeArray
型に対しても自動的に生成されるようになります。 -
Value
インターフェースのPut
からSet
へのリネーム:src/lib/reflect/value.go
において、IntValue
,UintValue
,FloatValue
,StringValue
,BoolValue
などの各種Value
インターフェースに定義されていた値設定メソッドの名前が、Put
からSet
に変更されました。 例:IntValue
インターフェースのPut(int)
がSet(int)
に。StringValue
インターフェースのPut(string)
がSet(string)
に。 これは、Go言語のAPI設計における一貫性を高めるための変更であり、より直感的な命名規則を採用しています。
-
ArrayValue
インターフェースとOpenArrayValueStruct
の機能拡張:ArrayValue
インターフェースにCap() int
メソッドが追加され、スライスの容量を取得できるようになりました。OpenArrayValueStruct
(動的な配列、スライスを表現する内部構造体)にCap()
メソッドの実装と、SetLen(len int)
メソッドが追加されました。SetLen
はスライスの長さを変更する機能を提供しますが、容量を超える長さへの設定はパニックを引き起こします。OpenArrayValueStruct
の内部で、実際のデータへのポインタと長さ、容量を保持するために*RuntimeArray
フィールドが導入されました。これにより、スライスの内部構造を直接操作できるようになります。
-
NewOpenArrayValue
関数の追加:src/lib/reflect/value.go
にNewOpenArrayValue(typ ArrayType, len, cap int) ArrayValue
という新しいエクスポート関数が追加されました。この関数は、指定された要素型、初期長さ、容量を持つ新しいスライス(ArrayValue
として)を動的に作成することを可能にします。これは、実行時にスライスを生成する必要がある場合に非常に有用です。 -
テストの更新:
src/lib/reflect/test.go
では、Put
からSet
へのメソッド名変更に対応するテストの修正が行われました。また、新しく追加されたNewOpenArrayValue
関数を使用してスライスを生成し、その要素にアクセス・設定するテストケースが追加され、新しいリフレクション機能が正しく動作することを確認しています。
これらの変更により、Goのreflect
パッケージは、スライスに対するより強力で柔軟な操作能力を獲得し、Go言語の汎用プログラミングの可能性を広げました。
コアとなるコードの変更箇所
src/lib/reflect/cast_amd64.s
@@ -171,3 +171,13 @@ TEXT reflect·PtrBoolToAddr(SB),7,$-8
MOVQ AX, 16(SP)
RET
+TEXT reflect·AddrToPtrRuntimeArray(SB),7,$-8
+ MOVQ 8(SP), AX
+ MOVQ AX, 16(SP)
+ RET
+
+TEXT reflect·PtrRuntimeArrayToAddr(SB),7,$-8
+ MOVQ 8(SP), AX
+ MOVQ AX, 16(SP)
+ RET
Addr
と*RuntimeArray
間の変換を行うアセンブリ関数が追加されています。
src/lib/reflect/value.go
@@ -14,6 +14,7 @@ import (
type Addr uint64 // TODO: where are ptrint/intptr etc?
// Conversion functions, implemented in assembler
+type RuntimeArray struct
func AddrToPtrAddr(Addr) *Addr
func AddrToPtrInt(Addr) *int
func AddrToPtrInt8(Addr) *int8
@@ -33,6 +34,8 @@ func AddrToPtrFloat64(Addr) *float64
func AddrToPtrFloat80(Addr) *float80
func AddrToPtrString(Addr) *string
func AddrToPtrBool(Addr) *bool
+func AddrToPtrRuntimeArray(Addr) *RuntimeArray
+func PtrRuntimeArrayToAddr(*RuntimeArray) Addr
export type Empty interface {} // TODO(r): Delete when no longer needed?
@@ -92,7 +95,7 @@ func MissingCreator(typ Type, addr Addr) Value {
export type IntValue interface {
Kind() int;
Get() int;
- Put(int);
+ Set(int);
Type() Type;
}
@@ -108,7 +111,7 @@ func (v *IntValueStruct) Get() int {
return *AddrToPtrInt(v.addr)
}
-func (v *IntValueStruct) Put(i int) {
+func (v *IntValueStruct) Set(i int) {
*AddrToPtrInt(v.addr) = i
}
// ... (他のValueインターフェースのPutからSetへの変更が続く)
@@ -533,13 +536,9 @@ export type ArrayValue interface {
Type() Type;
Open() bool;
Len() int;
+\tCap() int;
Elem(i int) Value;
-}\n-\n-type OpenArrayValueStruct struct {
-\tCommon;
-\telemtype Type;\
-\telemsize int;\
+\tSetLen(len int);\
}
/*
@@ -547,20 +546,43 @@ type OpenArrayValueStruct struct {
struct Array {
byte* array; // actual data
uint32 nel; // number of elements
+\t\t\tuint32 cap;\
};
*/
+type RuntimeArray struct {
+ data Addr;
+ len uint32;
+ cap uint32;
+}
+
+type OpenArrayValueStruct struct {
+ Common;
+ elemtype Type;
+ elemsize int;
+ array *RuntimeArray;
+}
func (v *OpenArrayValueStruct) Open() bool {
return true
}
func (v *OpenArrayValueStruct) Len() int {
- return int(*AddrToPtrInt32(v.addr+8));
+ return int(v.array.len);
+}
+
+func (v *OpenArrayValueStruct) Cap() int {
+ return int(v.array.cap);
+}
+
+func (v *OpenArrayValueStruct) SetLen(len int) {
+ if len > v.Cap() {
+ panicln("reflect: OpenArrayValueStruct.SetLen", len, v.Cap());
+ }
+ v.array.len = uint32(len);
}
func (v *OpenArrayValueStruct) Elem(i int) Value {
- base := *AddrToPtrAddr(v.addr);
- return NewValueAddr(v.elemtype, base + Addr(i * v.elemsize));
+ return NewValueAddr(v.elemtype, v.array.data + Addr(i * v.elemsize));
}
type FixedArrayValueStruct struct {
@@ -578,6 +600,13 @@ func (v *FixedArrayValueStruct) Len() int {
return v.len
}
+func (v *FixedArrayValueStruct) Cap() int {
+ return v.len
+}
+
+func (v *FixedArrayValueStruct) SetLen(len int) {
+}
+
func (v *FixedArrayValueStruct) Elem(i int) Value {
return NewValueAddr(v.elemtype, v.addr + Addr(i * v.elemsize));
return nil
@@ -592,6 +621,32 @@ func ArrayCreator(typ Type, addr Addr) Value {
\tv.typ = typ;
\tv.elemtype = arraytype.Elem();
\tv.elemsize = v.elemtype.Size();
+\t\tv.array = AddrToPtrRuntimeArray(addr);\
\treturn v;
}
\tv := new(FixedArrayValueStruct);
@@ -768,6 +798,32 @@ export func NewInitValue(typ Type) Value {
return NewValueAddr(typ, PtrUint8ToAddr(&data[0]));
}
+/*
+ Run-time representation of open arrays looks like this:
+ struct Array {
+ byte* array; // actual data
+ uint32 nel; // number of elements
+ uint32 cap; // allocated number of elements
+ };
+*/
+export func NewOpenArrayValue(typ ArrayType, len, cap int) ArrayValue {
+ if !typ.Open() {
+ return nil
+ }
+
+ array := new(RuntimeArray);
+ size := typ.Elem().Size() * cap;
+ if size == 0 {
+ size = 1;
+ }
+ data := new([]uint8, size);
+ array.data = PtrUint8ToAddr(&data[0]);
+ array.len = uint32(len);
+ array.cap = uint32(cap);
+
+ return NewValueAddr(typ, PtrRuntimeArrayToAddr(array));
+}
+
export func NewValue(e Empty) Value {
value, typestring := sys.reflect(e);
p, ok := typecache[typestring];
RuntimeArray
構造体の定義、AddrToPtrRuntimeArray
とPtrRuntimeArrayToAddr
の宣言、Put
からSet
へのメソッド名変更、ArrayValue
インターフェースへのCap()
とSetLen()
の追加、OpenArrayValueStruct
の内部構造の変更、そしてNewOpenArrayValue
関数の追加が行われています。
コアとなるコードの解説
RuntimeArray
構造体とアセンブリ関数
RuntimeArray
構造体は、Goのスライスが内部的にどのように表現されているかをreflect
パッケージが理解するための鍵となります。data
はスライスの基盤となる配列の先頭へのポインタ、len
は現在の要素数、cap
は割り当てられたメモリの最大容量を示します。
AddrToPtrRuntimeArray
とPtrRuntimeArrayToAddr
というアセンブリ関数は、Goの型システムをバイパスして、生のメモリアドレス(Addr
)とRuntimeArray
構造体へのポインタ(*RuntimeArray
)の間で安全に変換を行うために存在します。これにより、reflect
パッケージはスライスの内部表現に直接アクセスし、その長さや容量、基盤となるデータポインタを読み書きできるようになります。これは、Goの型安全性を維持しつつ、低レベルな操作を可能にするための重要なメカニズムです。
Put
からSet
へのメソッド名変更
これは、reflect
パッケージのAPI全体における命名規則の統一を目的とした変更です。Put
という名前は「値を置く」というニュアンスを持ちますが、Set
はより明確に「値を設定する」という動作を示します。この変更により、reflect
パッケージのAPIがより一貫性のあるものとなり、開発者にとって理解しやすくなりました。
ArrayValue
インターフェースとOpenArrayValueStruct
の拡張
ArrayValue
インターフェースにCap()
メソッドが追加されたことで、reflect
パッケージを通じてスライスの容量をプログラム的に取得できるようになりました。これは、スライスのメモリ割り当て状況を把握したり、容量に基づいた最適化を行ったりする際に役立ちます。
OpenArrayValueStruct
は、動的な配列(スライス)のreflect.Value
表現を内部的に管理する構造体です。この構造体が*RuntimeArray
フィールドを持つように変更されたことで、reflect
パッケージはスライスの内部的な長さ、容量、データポインタに直接アクセスできるようになりました。
SetLen(len int)
メソッドの追加は、reflect
パッケージを通じてスライスの長さを動的に変更する機能を提供します。ただし、この操作はスライスの容量を超える長さを設定しようとするとパニックを引き起こすため、安全な使用が求められます。
NewOpenArrayValue
関数
この関数は、reflect
パッケージの最も重要な追加機能の一つです。これにより、開発者は実行時に新しいスライスを動的に作成し、その初期長さと容量を指定できるようになります。これは、例えば、JSONやデータベースから読み込んだデータに基づいて、適切なサイズのスライスを動的に構築するようなシナリオで非常に強力な機能となります。この関数は、Goの型システムがコンパイル時に型を決定するのに対し、実行時に柔軟なデータ構造を生成する能力をreflect
パッケージに与えます。
これらの変更は、Go言語がその初期段階において、リフレクション機能をより堅牢で実用的なものにするための基盤を築いたことを示しています。特にスライスのようなGoのコアデータ構造に対する深いリフレクションサポートは、Goエコシステムにおける多くの汎用ライブラリの発展に貢献しました。
関連リンク
- Go言語の
reflect
パッケージ公式ドキュメント: https://pkg.go.dev/reflect - Go言語のスライスに関する公式ブログ記事 (より現代的な視点): https://go.dev/blog/slices-intro
参考にした情報源リンク
- GitHubコミットページ: https://github.com/golang/go/commit/e4f4ab0b8d2e221909cbf15c6867d4990f09e5a8
- Go言語の初期の設計に関する議論やドキュメント (当時の文脈を理解するため): Go言語の公式リポジトリの歴史的なコミットログや、Goの設計に関する初期のメーリングリストのアーカイブなどが参考になりますが、特定のURLは提供できません。
- Go言語のアセンブリに関するドキュメント: https://go.dev/doc/asm
- Go言語の内部構造に関する情報 (スライスの内部表現など): Goのソースコード自体が最も正確な情報源ですが、Goの内部構造を解説するブログ記事や書籍も参考になります。