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

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

このコミットは、Go言語の初期段階において、reflectパッケージにポインタが指す値を変更する機能を追加したものです。具体的には、reflect.PtrValue型(当時のAPI)にSetSubメソッドを導入し、ポインタが指す先の値をリフレクションを通じて設定できるようにしました。これにより、Goのreflectパッケージがより強力な動的型操作をサポートする方向へと進化する一歩となりました。

コミット

commit 419e1e05a1ad418c4f5526dee993e300f7551f46
Author: Rob Pike <r@golang.org>
Date:   Wed Nov 12 19:05:05 2008 -0800

    add some support for modifying what pointers point to
    
    R=rsc
    DELTA=27  (16 added, 11 deleted, 0 changed)
    OCL=19130
    CL=19132

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

https://github.com/golang/go/commit/419e1e05a1ad418c4f5526dee993e300f7551f46

元コミット内容

add some support for modifying what pointers point to

R=rsc
DELTA=27  (16 added, 11 deleted, 0 changed)
OCL=19130
CL=19132

変更の背景

このコミットは、Go言語がまだ公開される前の非常に初期の段階(2008年11月)に行われたものです。当時のGoのreflectパッケージは、現在のような成熟したAPIを持つに至る途上にありました。リフレクションは、プログラムの実行時に型情報を検査したり、値を動的に操作したりするための強力な機能です。ポインタが指す値を動的に変更する機能は、例えば、構造体のフィールドに値を設定する、インターフェースの基底値を変更する、あるいは汎用的なデータ操作ライブラリを構築する際に不可欠となります。

このコミット以前のreflectパッケージでは、ポインタが指す値を直接変更するメカニズムが不足していたと考えられます。そのため、reflect.PtrValue(当時のポインタを表すリフレクション型)に対して、その「サブ要素」(ポインタが指す値)を設定するSetSubメソッドを追加する必要がありました。これは、Goのリフレクションがより柔軟で実用的なツールとなるための重要なステップでした。

前提知識の解説

Go言語におけるポインタ

Go言語におけるポインタは、変数のメモリアドレスを保持する特殊な変数です。ポインタを使用することで、関数間で大きなデータをコピーせずに参照渡ししたり、データ構造を動的にリンクしたりすることができます。

  • &演算子:変数のメモリアドレスを取得し、その変数へのポインタを生成します。
  • *演算子:ポインタが指すメモリアドレスに格納されている値(ポインタの「デリファレンス」)を取得します。

Go言語のreflectパッケージ

reflectパッケージは、Goプログラムが自身の構造を検査し、実行時に値を操作するための機能を提供します。これにより、Goは動的な型チェックや汎用的なデータ処理を可能にします。

  • reflect.Type: Goの型の情報を表します。
  • reflect.Value: Goの値を表します。reflect.Valueは、その値の型情報(Type()メソッド)や、値そのもの(Interface()メソッド)を提供します。
  • Settability (設定可能性): reflect.Valueには「設定可能性 (settability)」という概念があります。これは、リフレクションを通じてそのreflect.Valueが表す元の値を変更できるかどうかを示します。CanSet()メソッドで確認でき、trueの場合のみSet()などの変更操作が可能です。通常、ポインタのデリファレンスによって得られたreflect.Valueは設定可能です。

reflectパッケージの進化とSetSubの位置づけ

このコミットが行われた2008年当時、GoのreflectパッケージのAPIはまだ流動的でした。現在のGoでは、ポインタが指す値を変更するには、reflect.ValueOf(&myVar).Elem().Set(newValue)のような形式を使用します。ここで、Elem()メソッドはポインタをデリファレンスし、ポインタが指す値のreflect.Valueを返します。そして、そのreflect.Valueに対してSet()メソッドを呼び出すことで、値を設定します。

このコミットで導入されたSetSubメソッドは、現在のElem().Set()に相当する機能の初期の実装であったと考えられます。当時のreflect.PtrValueという型が、現在のreflect.Valueがポインタを表す場合の振る舞いを担っていたと推測されます。SetSubは、ポインタが指す「サブ要素」を設定するという、より直接的な命名がされていました。

技術的詳細

このコミットの主要な変更点は、src/lib/reflect/value.goファイルにPtrValueStruct(当時のreflect.PtrValueの実装)にSetSubメソッドが追加されたことです。

SetSubメソッドの定義は以下の通りです。

func (v *PtrValueStruct) SetSub(subv Value)  {
	a := v.typ.(PtrType).Sub().String();
	b := subv.Type().String();
	if a != b {
		panicln("reflect: incompatible types in PtrValue.SetSub:", a, b);
	}
	*AddrToPtrAddr(v.addr) = subv.Addr();
}

このメソッドは、以下の処理を行っています。

  1. 型の一致性チェック:

    • v.typ.(PtrType).Sub().String(): PtrValueStructが表すポインタの基底型(ポインタが指す型)の文字列表現を取得します。
    • subv.Type().String(): SetSubに渡されたsubv(設定しようとしている新しい値)の型の文字列表現を取得します。
    • if a != b: これら二つの型が一致しない場合、paniclnを呼び出して実行時パニックを発生させます。これは、ポインタが指す型と異なる型の値を設定しようとする不正な操作を防ぐための重要な型安全性チェックです。
  2. ポインタの指す値の変更:

    • AddrToPtrAddr(v.addr): v.addrPtrValueStructが保持するポインタのアドレス(つまり、ポインタ変数自身のアドレス)です。この関数は、そのアドレスを適切なポインタ型に変換します。
    • *AddrToPtrAddr(v.addr): 変換されたポインタをデリファレンスし、ポインタが現在指しているメモリアドレスにアクセスします。
    • = subv.Addr(): subv(設定しようとしている新しい値)のメモリアドレスを、デリファレンスしたポインタの指す先に代入します。これにより、ポインタが新しい値を指すように変更されます。

この実装は、ポインタが指す値を直接変更するという、リフレクションの強力な機能を提供します。同時に、厳密な型チェックを行うことで、Goの型安全性の原則を維持しようとしています。

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

diff --git a/src/lib/reflect/test.go b/src/lib/reflect/test.go
index 7088094383..7b97608dca 100644
--- a/src/lib/reflect/test.go
+++ b/src/lib/reflect/test.go
@@ -91,17 +91,6 @@ func main() {\n 	var s string;\n 	var t reflect.Type;\n \n-{\n-\tvar ip *int32;\n-\tvar i int32 = 1234;\n-\tvip := reflect.NewValue(&ip);\n-\tvi := reflect.NewValue(i);\n-\tvip.(reflect.PtrValue).Sub().(reflect.PtrValue).SetSub(vi);\n-\tif *ip != 1234 {\n-\t\tpanicln(\"SetSub failure\", *ip);\n-\t}\n-}\n-\n 	// Types\n \ttypedump(\"missing\", \"$missing$\");\n \ttypedump(\"int\", \"int\");\n@@ -205,6 +194,17 @@ func main() {\n \t\tassert(reflect.ValueToString(value.(reflect.PtrValue).Sub()), \"main.AA·test{1, 2, 3, 4, 123, 6, 7, 8, 9, 10}\");\n \t}\n \n+\t{\n+\t\tvar ip *int32;\n+\t\tvar i int32 = 1234;\n+\t\tvip := reflect.NewValue(&ip);\n+\t\tvi := reflect.NewValue(i);\n+\t\tvip.(reflect.PtrValue).Sub().(reflect.PtrValue).SetSub(vi);\n+\t\tif *ip != 1234 {\n+\t\t\tpanicln(\"SetSub failure\", *ip);\n+\t\t}\n+\t}\n+\n \tvar pt reflect.PtrType;\n \tvar st reflect.StructType;\n \tvar mt reflect.MapType;\ndiff --git a/src/lib/reflect/value.go b/src/lib/reflect/value.go
index bace93b6d1..1877d1015a 100644
--- a/src/lib/reflect/value.go
+++ b/src/lib/reflect/value.go
@@ -514,6 +514,11 @@ func (v *PtrValueStruct) Sub() Value {\n }\n \n func (v *PtrValueStruct) SetSub(subv Value)  {\n+\ta := v.typ.(PtrType).Sub().String();\n+\tb := subv.Type().String();\n+\tif a != b {\n+\t\tpanicln(\"reflect: incompatible types in PtrValue.SetSub:\", a, b);\n+\t}\n \t*AddrToPtrAddr(v.addr) = subv.Addr();
 }\n \n ```

## コアとなるコードの解説

### `src/lib/reflect/test.go` の変更

このファイルは`reflect`パッケージのテストコードです。変更内容は、`SetSub`のテストケースをファイルの別の位置に移動しただけです。機能的な変更はありませんが、これは`SetSub`メソッドが導入されたことによるテストの再配置を示しています。

テストケースのコードは以下の通りです。

```go
{
	var ip *int32; // int32へのポインタを宣言
	var i int32 = 1234; // int32型の変数iを初期化
	vip := reflect.NewValue(&ip); // ip(*int32型)へのポインタのreflect.Valueを取得
	vi := reflect.NewValue(i); // i(int32型)のreflect.Valueを取得
	// vipは**int32型を指すreflect.Value。
	// .Sub()で*int32型を指すreflect.Value(ip自身)を取得。
	// さらに.Sub()でint32型を指すreflect.Value(ipが指す値)を取得。
	// そのreflect.Valueに対してSetSub(vi)を呼び出し、iの値を設定。
	vip.(reflect.PtrValue).Sub().(reflect.PtrValue).SetSub(vi);
	if *ip != 1234 { // ipが指す値が1234になっているか確認
		panicln("SetSub failure", *ip);
	}
}

このテストは、reflect.NewValue(&ip)**int32型のreflect.Valueを取得し、そこから二段階のSub()呼び出し(現在のElem()に相当)を経て、最終的に*int32が指すint32型の値をSetSubで設定できることを検証しています。

src/lib/reflect/value.go の変更

このファイルはreflectパッケージの核心部分であり、reflect.Valueの実装が含まれています。

  • SetSubメソッドの追加: 前述の「技術的詳細」セクションで解説した通り、PtrValueStructSetSubメソッドが追加されました。このメソッドは、ポインタが指す値を別のreflect.Valueが表す値に設定する機能を提供します。
  • 型チェックの導入: SetSubメソッド内で、設定しようとしている値の型が、ポインタが指す基底型と一致するかどうかの厳密なチェックが導入されました。これにより、実行時の型不一致によるエラーを防ぎ、Goの型安全性を維持しています。型が一致しない場合はpaniclnが呼び出され、プログラムが異常終了します。

この変更により、Goのリフレクションは、ポインタが指す値を動的に変更するという、より高度な操作を安全に行えるようになりました。

関連リンク

参考にした情報源リンク