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

[インデックス 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

元コミット内容

InterfaceValueValue() 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.Printlnfmt.Printffmt.Sprintfなどの関数が含まれ、様々な型の値を整形して出力する機能を持っています。fmtパッケージは、出力する値の型情報をreflectパッケージを通じて取得し、それに基づいて適切なフォーマットを適用します。

技術的詳細

このコミットの核心は、reflectパッケージとfmtパッケージ間の連携を改善し、interface{}型の値がfmtパッケージによって適切に表示されるようにすることです。

  1. 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に変換しています。
  2. 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.Valuenilでなければ、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.ValueKind()reflect.InterfaceKind(つまり、値がインターフェース型である)と判断した場合の処理を定義しています。
    • 変更前:
      • inter := field.(reflect.InterfaceValue).Get();reflect.InterfaceValueからGet()メソッドを使って、内部のinterface{}値を取得していました。
      • if inter == nil { s = "<nil>" } else { s = "<non-nil interface>"; }internilでなければ、単に<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); }:取得したvaluenilであれば<nil>を出力します。しかし、valuenilでなければ、p.printField(value)を再帰的に呼び出します。これにより、fmtパッケージはインターフェースの内部にある具体的な値(例: int, stringなど)を、その値のKindに応じた適切なフォーマットルールに従って表示できるようになります。この再帰的な呼び出しが、インターフェースの「中身」を正確に表示するための鍵となります。

これらの変更により、Goのfmtパッケージは、reflectパッケージの新しい機能を利用して、interface{}型の値をより詳細かつ正確に表示できるようになり、Goプログラムのデバッグやログ出力の利便性が大幅に向上しました。

関連リンク

参考にした情報源リンク

  • Go言語のreflectパッケージの初期バージョンに関する情報:
  • Go言語のfmt.Printlninterface{}の歴史に関する情報:
  • Go言語のインターフェースの内部表現に関する情報:
  • 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の初期の設計に関するブログ記事やドキュメント)を参考にしました。