[インデックス 1965] ファイルの概要
このコミットは、Go言語の初期段階におけるreflect
パッケージとfmt
パッケージの連携に関する重要な改善を含んでいます。具体的には、reflect.InterfaceValue
型にValue()
メソッドを追加し、fmt
パッケージがインターフェースの内部値をより適切に表示できるように変更しています。
変更されたファイルは以下の通りです。
src/lib/fmt/fmt_test.go
:fmt
パッケージのテストファイル。インターフェースを含む配列のテストケースが追加・修正されています。src/lib/fmt/print.go
:fmt
パッケージの出力処理を司るファイル。reflect.InterfaceKind
の値を表示するロジックが変更されています。src/lib/reflect/all_test.go
:reflect
パッケージのテストファイル。InterfaceValue.Value()
メソッドのテストケースが追加されています。src/lib/reflect/value.go
:reflect
パッケージのコアとなるファイル。reflect.InterfaceValue
インターフェースにValue()
メソッドが追加され、その実装が提供されています。
コミット
commit ac6ebfdea9e52a82bb55f7eb28c79619e2ffba10
Author: Russ Cox <rsc@golang.org>
Date: Mon Apr 6 21:28:04 2009 -0700
add method Value() Value to InterfaceValue.
use Value() in print to print underlying value
from interface.
before:
package main
import "fmt"
func main() {
x := []interface{} {1, "hello", 2.5};
fmt.Println(x[0], x[1], x[2], x);
}
1 hello 2.5 [<non-nil interface> <non-nil interface> <non-nil interface>]
after:
1 hello 2.5 [1 hello 2.5]
R=r
DELTA=44 (22 added, 16 deleted, 6 changed)
OCL=27139
CL=27141
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ac6ebfdea9e52a82bb55f7eb28c79619e2ffba10
元コミット内容
InterfaceValue
にValue() Value
メソッドを追加しました。
print
関数でValue()
を使用して、インターフェースから基になる値を出力するようにしました。
変更前:
package main
import "fmt"
func main() {
x := []interface{} {1, "hello", 2.5};
fmt.Println(x[0], x[1], x[2], x);
}
出力:
1 hello 2.5 [<non-nil interface> <non-nil interface> <non-nil interface>]
変更後:
1 hello 2.5 [1 hello 2.5]
変更の背景
このコミットが行われた2009年当時、Go言語はまだ開発の初期段階にありました。fmt
パッケージのPrintln
などの関数がinterface{}
型の値を表示する際に、その内部に保持されている具体的な値ではなく、<non-nil interface>
のような汎用的な文字列を出力してしまう問題がありました。
これは、fmt
パッケージがreflect
パッケージを通じてインターフェースの情報を取得する際に、インターフェースが持つ「型」の情報は取得できても、「値」そのものをreflect.Value
として直接取得する効率的な手段が不足していたためと考えられます。reflect.InterfaceValue
にはGet()
メソッドがありましたが、これはinterface{}
型の値を返すため、fmt
パッケージがその値をさらにreflect.Value
として処理するには追加の変換が必要でした。
この問題は、ユーザーがインターフェースの具体的な内容をデバッグやログ出力で確認する際に不便であり、Go言語の「実用性」という設計思想に反していました。そのため、fmt
パッケージがインターフェースの内部値をより直感的に表示できるように、reflect
パッケージの機能拡張が必要とされました。
前提知識の解説
Goのinterface{}
(空インターフェース)
Go言語におけるinterface{}
(空インターフェース)は、任意の型の値を保持できる特別なインターフェースです。これは、型情報と値のペアとして内部的に表現されます。
- 型情報 (Type Information): インターフェースが現在保持している具体的な値の型(例:
int
,string
,float64
など)を指します。 - 値 (Value): インターフェースが現在保持している具体的な値そのものを指します。
インターフェースがnil
であるのは、この両方の情報がnil
である場合のみです。どちらか一方でもnil
でなければ、インターフェース自体はnil
ではありません。
reflect
パッケージ
reflect
パッケージは、Goプログラムが実行時に自身の構造(型、値、メソッドなど)を検査・操作するための機能を提供します。これは、Goの静的型付けシステムを補完し、汎用的なデータ処理やシリアライズ/デシリアライズ、テストフレームワークなどの実装に利用されます。
reflect.Type
: Goの型のメタデータを表します。型の名前、カテゴリ(Kind
)、メソッド、フィールドなどの情報を含みます。reflect.Value
: Goの値の実行時表現を表します。値の型情報と、その値自体へのアクセスを提供します。reflect.Value
は、Goのあらゆる型の値をラップできます。reflect.InterfaceValue
(当時のGoにおける型): このコミットが行われた当時のGoのreflect
パッケージには、インターフェース型の値を特別に扱うためのInterfaceValue
という型が存在しました。これは、現在のreflect.Value
がインターフェースを表現する際の内部的な詳細を抽象化したものと考えられます。
fmt
パッケージ
fmt
パッケージは、GoにおけるフォーマットされたI/O(入出力)を提供します。fmt.Println
、fmt.Printf
、fmt.Sprintf
などの関数が含まれ、様々な型の値を整形して出力する機能を持っています。fmt
パッケージは、出力する値の型情報をreflect
パッケージを通じて取得し、それに基づいて適切なフォーマットを適用します。
技術的詳細
このコミットの核心は、reflect
パッケージとfmt
パッケージ間の連携を改善し、interface{}
型の値がfmt
パッケージによって適切に表示されるようにすることです。
-
reflect.InterfaceValue
へのValue() Value
メソッドの追加:- 以前の
reflect.InterfaceValue
には、インターフェースが保持する具体的なinterface{}
型の値を取得するGet() interface{}
メソッドがありました。しかし、fmt
パッケージがその値をさらに処理するためには、reflect.Value
型として取得できる方が効率的でした。 - このコミットでは、
InterfaceValue
インターフェースにValue() Value
という新しいメソッドが追加されました。このメソッドは、インターフェースが保持する具体的な値をreflect.Value
型として返します。 - 実装としては、
Get()
で取得したinterface{}
型の値をreflect.NewValue()
関数(これも当時のreflect
パッケージに存在した関数で、Goの値をreflect.Value
に変換する)に渡すことで、reflect.Value
に変換しています。
- 以前の
-
fmt
パッケージでのValue()
メソッドの利用:src/lib/fmt/print.go
内のprintField
関数は、様々な型の値をフォーマットして出力する役割を担っています。- この関数内で、
reflect.InterfaceKind
(インターフェース型)の値を処理する部分が変更されました。 - 変更前は、
field.(reflect.InterfaceValue).Get()
でinterface{}
型の値を取得し、それがnil
でなければ<non-nil interface>
という文字列を出力していました。これは、fmt
がそのinterface{}
型の値の具体的な型や内容を適切に処理できていなかったことを示しています。 - 変更後は、
field.(reflect.InterfaceValue).Value()
を呼び出して、インターフェースが保持する具体的な値をreflect.Value
として取得するようになりました。 - 取得した
reflect.Value
がnil
でなければ、p.printField(value)
を再帰的に呼び出すことで、インターフェースの内部にある具体的な値(例:int
,string
,float64
など)を、その型に応じた適切なフォーマットで表示できるようになりました。これにより、fmt
パッケージはインターフェースの「中身」を「見える化」できるようになりました。
この変更により、fmt.Println
などがinterface{}
型の値を表示する際に、その内部に格納されている実際の値(例: 1
, "hello"
, 2.5
)を正確に反映できるようになり、デバッグやログ出力の利便性が大幅に向上しました。
コアとなるコードの変更箇所
src/lib/reflect/value.go
InterfaceValue
インターフェースにValue() Value
メソッドが追加され、interfaceValueStruct
にその実装が追加されました。
--- a/src/lib/reflect/value.go
+++ b/src/lib/reflect/value.go
@@ -744,6 +746,7 @@ func structCreator(typ Type, addr Addr) Value {
type InterfaceValue interface {
Value;
Get() interface {}; // Get the underlying interface{} value.
+ Value() Value;
}
type interfaceValueStruct struct {
@@ -754,6 +757,14 @@ func (v *interfaceValueStruct) Get() interface{} {
return *(*interface{})(v.addr)
}
+func (v *interfaceValueStruct) Value() Value {
+ i := v.Get();
+ if i == nil {
+ return nil;
+ }
+ return NewValue(i);
+}
+
func interfaceCreator(typ Type, addr Addr) Value {
return &interfaceValueStruct{ commonValue{InterfaceKind, typ, addr} }
}
src/lib/fmt/print.go
printField
関数内で、reflect.InterfaceKind
の処理が変更されました。
--- a/src/lib/fmt/print.go
+++ b/src/lib/fmt/print.go
@@ -451,12 +451,11 @@ func (p *pp) printField(field reflect.Value) (was_string bool) {
}
p.add('}');
case reflect.InterfaceKind:
- inter := field.(reflect.InterfaceValue).Get();
- if inter == nil {
+ value := field.(reflect.InterfaceValue).Value();
+ if value == nil {
s = "<nil>"
} else {
- // should never happen since a non-nil interface always has a type
- s = "<non-nil interface>";
+ return p.printField(value);
}
default:
s = "?" + field.Type().String() + "?";
コアとなるコードの解説
src/lib/reflect/value.go
の変更
-
type InterfaceValue interface { ... Value() Value; }
:- これは、
reflect
パッケージ内のInterfaceValue
というインターフェース定義に、新たにValue() Value
というメソッドを追加したものです。 - このメソッドは、
InterfaceValue
がラップしている実際のGoの値を、再びreflect.Value
型として返すことを意図しています。これにより、リフレクションを通じてインターフェースの内部値にアクセスし、それをさらにリフレクションの機能で操作できるようになります。
- これは、
-
func (v *interfaceValueStruct) Value() Value { ... }
:- これは、
InterfaceValue
インターフェースの実装であるinterfaceValueStruct
に、上記で追加されたValue()
メソッドの具体的なロジックを提供します。 i := v.Get();
:まず、既存のGet()
メソッドを呼び出して、インターフェースが保持している生のinterface{}
型の値を取得します。if i == nil { return nil; }
:取得した値がnil
であれば、reflect.Value
としてもnil
を返します。return NewValue(i);
:取得したinterface{}
型の値i
を、当時のreflect
パッケージに存在したNewValue()
関数に渡します。NewValue()
は、任意のGoの値をreflect.Value
型に変換する役割を担っていました。これにより、インターフェースの内部値がreflect.Value
として適切にラップされ、返されます。
- これは、
src/lib/fmt/print.go
の変更
case reflect.InterfaceKind:
ブロックの変更:- このコードブロックは、
fmt
パッケージがreflect.Value
のKind()
がreflect.InterfaceKind
(つまり、値がインターフェース型である)と判断した場合の処理を定義しています。 - 変更前:
inter := field.(reflect.InterfaceValue).Get();
:reflect.InterfaceValue
からGet()
メソッドを使って、内部のinterface{}
値を取得していました。if inter == nil { s = "<nil>" } else { s = "<non-nil interface>"; }
:inter
がnil
でなければ、単に<non-nil interface>
という文字列を割り当てていました。これは、インターフェースが非nil
であることはわかるものの、その具体的な中身は表示されないという問題を引き起こしていました。コメントにある// should never happen since a non-nil interface always has a type
は、非nil
インターフェースは常に型を持つため、このパスが実行されるべきではないという当時の開発者の認識を示唆していますが、実際にはfmt
がその型と値を適切に処理できていなかったため、このような出力になっていました。
- 変更後:
value := field.(reflect.InterfaceValue).Value();
:新しく追加されたValue()
メソッドを呼び出して、インターフェースが保持する具体的な値をreflect.Value
型として取得します。if value == nil { s = "<nil>" } else { return p.printField(value); }
:取得したvalue
がnil
であれば<nil>
を出力します。しかし、value
がnil
でなければ、p.printField(value)
を再帰的に呼び出します。これにより、fmt
パッケージはインターフェースの内部にある具体的な値(例:int
,string
など)を、その値のKind
に応じた適切なフォーマットルールに従って表示できるようになります。この再帰的な呼び出しが、インターフェースの「中身」を正確に表示するための鍵となります。
- このコードブロックは、
これらの変更により、Goのfmt
パッケージは、reflect
パッケージの新しい機能を利用して、interface{}
型の値をより詳細かつ正確に表示できるようになり、Goプログラムのデバッグやログ出力の利便性が大幅に向上しました。
関連リンク
- Go言語の
reflect
パッケージに関する公式ドキュメント (現代版): https://pkg.go.dev/reflect - Go言語の
fmt
パッケージに関する公式ドキュメント (現代版): https://pkg.go.dev/fmt - Go言語のインターフェースに関する解説 (現代版): https://go.dev/tour/methods/10
参考にした情報源リンク
- Go言語の
reflect
パッケージの初期バージョンに関する情報:- https://go.dev/blog/laws-of-reflection (Goの反射の法則 - 2009年の記事ではないが、初期の設計思想を理解するのに役立つ)
- Go言語の
fmt.Println
とinterface{}
の歴史に関する情報:- https://swtch.com/~rsc/regexp/regexp1.html (Go言語の歴史に関するRuss Cox氏のブログ記事)
- Go言語のインターフェースの内部表現に関する情報:
- https://swtch.com/~rsc/go-interface.html (Goのインターフェースの内部表現に関するRuss Cox氏のブログ記事)
- Google検索結果:
- "Go reflect.InterfaceValue Value() method 2009"
- "Go fmt.Println interface{} printing history 2009"
- "Go reflect package early versions 2009"
- "Go interface internal representation 2009"
- これらの検索結果から得られた情報(特にGoの初期の設計に関するブログ記事やドキュメント)を参考にしました。