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

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

このコミットは、Go言語のreflectパッケージに新たなメソッドValue.Slice3Value.SetCapを追加し、既存のValue.Sliceメソッドを更新するものです。これにより、Go 1.2で導入された3インデックススライス構文x[i:j:k]capを指定するスライス操作)をreflectパッケージ経由でサポートします。

変更されたファイルは以下の通りです。

  • src/pkg/reflect/all_test.go: 68行追加
  • src/pkg/reflect/value.go: 85行追加、12行削除

合計で141行の追加と12行の削除が行われています。

コミット

commit 4d8aefde470de630a1f6f6fc2c481fedb4a293c8
Author: Russ Cox <rsc@golang.org>
Date:   Mon Jul 1 20:32:53 2013 -0400

    reflect: add Value.Slice3 and Value.SetCap methods, to match x[i:j:k]
    
    Design doc at golang.org/s/go12slice.
    
    R=golang-dev, r, nightlyone
    CC=golang-dev
    https://golang.org/cl/10761045

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

https://github.com/golang/go/commit/4d8aefde470de630a1f6f6fc2c481fedb4a293c8

元コミット内容

reflect: add Value.Slice3 and Value.SetCap methods, to match x[i:j:k]

Design doc at golang.org/s/go12slice.

R=golang-dev, r, nightlyone
CC=golang-dev
https://golang.org/cl/10761045

変更の背景

このコミットの主な背景は、Go 1.2で導入された新しいスライス構文x[i:j:k](3インデックススライス)をreflectパッケージでサポートすることです。従来の2インデックススライスx[i:j]では、結果として得られるスライスの長さ(len)と容量(cap)は、元のスライスの容量から自動的に決定されていました。しかし、3インデックススライスでは、kを指定することで、結果スライスの容量を明示的に制御できるようになります。

reflectパッケージは、Goプログラムが自身の構造を検査し、実行時に値を操作するための機能を提供します。このパッケージが言語の新しい機能(この場合は3インデックススライス)をサポートしない場合、リフレクションを使用するプログラムは、その新しい機能の恩恵を受けることができません。したがって、reflectパッケージにValue.Slice3メソッドを追加し、3インデックススライス操作をリフレクション経由で実行できるようにすることが必要でした。

また、スライスの容量を直接設定するためのValue.SetCapメソッドも追加されています。これは、リフレクションを通じてスライスの容量を動的に変更するシナリオに対応するためです。これらの変更は、Go言語の柔軟性とリフレクション機能の完全性を高めることを目的としています。

前提知識の解説

Goのスライス(Slice)

Go言語のスライスは、配列をラップした動的なビューであり、Goプログラミングにおいて非常に重要なデータ構造です。スライスは、基になる配列の一部を参照し、その長さ(len)と容量(cap)を持ちます。

  • 長さ(len: スライスに含まれる要素の数。
  • 容量(cap: スライスの最初の要素から、基になる配列の末尾までの要素の数。これは、スライスが再割り当てなしで拡張できる最大サイズを示します。

スライスは、内部的には「スライスヘッダ」と呼ばれる小さな構造体で表現されます。このヘッダは以下の3つの要素から構成されます。

  1. データポインタ(Data Pointer): 基になる配列の最初の要素へのポインタ。
  2. 長さ(Length): スライスの現在の長さ。
  3. 容量(Capacity): スライスの容量。

スライスを別の変数に代入したり、関数に引数として渡したりすると、このスライスヘッダがコピーされます。しかし、コピーされたヘッダも元のヘッダと同じ基になる配列を指しているため、一方のスライスを通じて基になる配列の要素を変更すると、もう一方のスライスからもその変更が見えます。

スライス操作 x[i:j]x[i:j:k]

  • 2インデックススライス x[i:j]:

    • i: スライスの開始インデックス(inclusive)。
    • j: スライスの終了インデックス(exclusive)。
    • 結果のスライスの長さは j - i となります。
    • 結果のスライスの容量は、元のスライスの容量から i を引いた値となります。つまり、cap(x[i:j]) == cap(x) - i です。
  • 3インデックススライス x[i:j:k]:

    • i: スライスの開始インデックス(inclusive)。
    • j: スライスの終了インデックス(exclusive)。
    • k: 結果のスライスの容量の終了インデックス(exclusive)。
    • 結果のスライスの長さは j - i となります。
    • 結果のスライスの容量は k - i となります。
    • この構文は、特にappend操作などで、基になる配列の再割り当てを避けるために、より小さい容量を持つスライスを作成したい場合に有用です。例えば、make([]T, length, capacity)でスライスを作成するのと同様に、既存のスライスから特定の容量を持つ新しいスライスを作成できます。

reflectパッケージ

reflectパッケージは、Goプログラムが実行時に型情報(Type)と値情報(Value)を操作するための機能を提供します。これにより、ジェネリックなプログラミングや、構造体のフィールドへのアクセス、メソッドの呼び出しなどを動的に行うことができます。

  • reflect.Value: Goの任意の値を表す構造体です。この構造体を通じて、値の型、内容、および操作(例:フィールドの設定、メソッドの呼び出し)を行うことができます。
  • reflect.Kind: Goの組み込み型(例:Int, String, Slice, Arrayなど)を表す列挙型です。
  • reflect.ValueOf(i interface{}) Value: 任意のGoの値をreflect.Value型に変換します。
  • reflect.Value.Interface() interface{}: reflect.Valueを元のGoのインターフェース型に戻します。

unsafeパッケージ

unsafeパッケージは、Goの型安全性をバイパスする低レベルな操作を可能にします。これには、ポインタとuintptr間の変換や、任意の型へのポインタのキャストなどが含まれます。reflectパッケージのような低レベルな操作を行うライブラリでは、パフォーマンスや特定の機能を実現するためにunsafeパッケージが使用されることがあります。

このコミットでは、スライスヘッダ(SliceHeader)を直接操作するためにunsafe.Pointerが使用されています。SliceHeaderは、スライスの内部表現(データポインタ、長さ、容量)を定義する構造体で、reflectパッケージがスライスを操作する際に利用されます。

技術的詳細

このコミットは、reflectパッケージのValue型に、Go 1.2で導入された3インデックススライス構文x[i:j:k]に対応するSlice3メソッドと、スライスの容量を直接設定するSetCapメソッドを追加します。また、既存のSliceメソッドも、引数名をbeg, endからi, jに変更し、より一般的なスライス操作の表記に合わせるように修正されています。

Value.SetCap(n int) メソッド

このメソッドは、reflect.Valueが表すスライスの容量をnに設定します。

  • 前提条件:
    • vKindSliceである必要があります。それ以外の場合、パニックが発生します。
    • vが変更可能(assignable)である必要があります。
  • パニック条件:
    • nがスライスの現在の長さ(len)より小さい場合。
    • nがスライスの現在の容量(cap)より大きい場合。
    • これらの条件は、スライスの整合性を保つために重要です。容量は常に長さ以上であり、既存の容量を超えることはできません(その場合は再割り当てが必要となり、それはSetCapの役割ではありません)。
  • 実装:
    • 内部的には、v.valreflect.Valueが保持する値へのポインタ)を*SliceHeader型にキャストし、そのCapフィールドを直接nに設定します。これはunsafeパッケージを利用して行われます。

Value.Slice3(i, j, k int) メソッド

このメソッドは、reflect.Valueが表す配列またはスライスに対して、3インデックススライス操作v[i:j:k]を実行し、結果として新しいreflect.Value(スライス)を返します。

  • 前提条件:
    • vKindArrayまたはSliceである必要があります。それ以外の場合、パニックが発生します。
    • vが配列の場合、アドレス可能(addressable)である必要があります。
  • パニック条件:
    • i, j, kのインデックスが範囲外の場合。具体的には、0 <= i <= j <= k <= capの条件が満たされない場合。
  • 実装:
    • vKindに応じて、基になるデータポインタ(base)、元の容量(cap)、および要素の型情報(typ)を取得します。
    • 新しいスライスヘッダを構築し、Dataポインタをbase + i * elemSizeに、Lenj - iに、Capk - iに設定します。
    • この操作もunsafe.Pointerを使用して、スライスヘッダを直接操作します。
    • 結果として得られる新しいスライスを表すreflect.Valueを返します。

Value.Slice(i, j int) メソッドの変更

既存のValue.Sliceメソッドは、2インデックススライス操作v[i:j]を実行します。このコミットでは、引数名がbeg, endからi, jに変更され、より一般的なスライス表記に統一されました。機能的な変更はありませんが、コードの可読性と一貫性が向上しています。

これらの変更により、reflectパッケージはGo言語の最新のスライス構文を完全にサポートし、リフレクションを利用するプログラムがより柔軟にスライスを操作できるようになります。

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

src/pkg/reflect/all_test.go

  • TestSlice3関数が追加されました。このテストは、Value.Slice3メソッドの動作を検証します。
    • スライスと配列の両方に対してSlice3を呼び出し、結果のlencapが期待通りであるか、および内容が正しいかを確認します。
    • 不正なインデックスが指定された場合にパニックが発生することを確認するshouldPanicテストも含まれています。
    • 文字列に対してSlice3を呼び出すとパニックが発生することもテストされています(文字列は3インデックススライスをサポートしないため)。
  • TestSetLenCap関数が追加されました。このテストは、Value.SetLenと新しく追加されたValue.SetCapメソッドの動作を検証します。
    • スライスに対してSetLenSetCapを呼び出し、lencapが正しく更新されることを確認します。
    • 不正な値(例:lenより小さいcapcapより大きいlen)が指定された場合にパニックが発生することを確認するshouldPanicテストも含まれています。
    • 配列に対してSetLenSetCapを呼び出すとパニックが発生することもテストされています(配列は固定長のため)。

src/pkg/reflect/value.go

  • Value.SetCap(n int) メソッドが追加されました。
    • v.mustBeAssignable()v.mustBe(Slice)で、Valueがスライスであり、かつ変更可能であることを確認します。
    • nが現在の長さより小さいか、現在の容量より大きい場合にパニックを発生させます。
    • *SliceHeaderにキャストしてs.Cap = nで容量を設定します。
  • Value.Slice(i, j int) メソッドの引数名がbeg, endからi, jに変更されました。
    • 内部のインデックスチェックもi, jを使用するように更新されました。
    • 文字列スライスの場合のインデックスチェックもi, jを使用するように更新されました。
  • Value.Slice3(i, j, k int) メソッドが追加されました。
    • v.kind()に基づいて、ArrayまたはSlice型であることを確認し、それ以外の場合はパニックを発生させます。
    • 配列の場合、v.flag&flagAddr == 0でアドレス可能であることを確認します。
    • インデックスi, j, k0 <= i <= j <= k <= capの範囲内にあることを確認し、範囲外の場合はパニックを発生させます。
    • 新しいスライスヘッダを構築し、Data, Len, Capフィールドを計算された値で設定します。
    • Value{typ.common(), unsafe.Pointer(&x), fl}で新しいreflect.Valueを構築して返します。

コアとなるコードの解説

Value.SetCap の実装 (src/pkg/reflect/value.go)

// SetCap sets v's capacity to n.
// It panics if v's Kind is not Slice or if n is smaller than the length or
// greater than the capacity of the slice.
func (v Value) SetCap(n int) {
	v.mustBeAssignable() // Valueが変更可能であることを確認
	v.mustBe(Slice)      // Valueがスライス型であることを確認
	s := (*SliceHeader)(v.val) // Valueの内部ポインタをSliceHeaderにキャスト
	if n < int(s.Len) || n > int(s.Cap) { // 新しい容量nが現在の長さより小さいか、現在の容量より大きい場合
		panic("reflect: slice capacity out of range in SetCap") // パニック
	}
	s.Cap = n // SliceHeaderのCapフィールドをnに設定
}

このコードは、reflect.Valueが表すスライスの容量を直接変更します。v.valreflect.Valueの内部的なフィールドで、Goの実際の値へのポインタを保持しています。これを*SliceHeaderにキャストすることで、スライスの内部構造(データポインタ、長さ、容量)に直接アクセスし、Capフィールドを更新しています。パニック条件は、スライスの整合性を保つために非常に重要です。

Value.Slice3 の実装 (src/pkg/reflect/value.go)

// Slice3 is the 3-index form of the slice operation: it returns v[i:j:k].
// It panics if v's Kind is not Array or Slice, or if v is an unaddressable array,
// or if the indexes are out of bounds.
func (v Value) Slice3(i, j, k int) Value {
	var (
		cap  int
		typ  *sliceType
		base unsafe.Pointer
	)
	switch kind := v.kind(); kind {
	default:
		panic(&ValueError{"reflect.Value.Slice3", kind}) // ArrayまたはSlice以外はパニック

	case Array:
		if v.flag&flagAddr == 0 {
			panic("reflect.Value.Slice: slice of unaddressable array") // アドレス不可能な配列はパニック
		}
		tt := (*arrayType)(unsafe.Pointer(v.typ)) // 配列の型情報を取得
		cap = int(tt.len)                         // 配列の長さを容量とする
		typ = (*sliceType)(unsafe.Pointer(tt.slice)) // 配列の要素型からスライス型を構築
		base = v.val                              // 配列の基底ポインタ

	case Slice:
		typ = (*sliceType)(unsafe.Pointer(v.typ)) // スライスの型情報を取得
		s := (*SliceHeader)(v.val)                // スライスヘッダを取得
		base = unsafe.Pointer(s.Data)             // スライスの基底ポインタ
		cap = s.Cap                               // スライスの容量
	}

	if i < 0 || j < i || k < j || k > cap { // インデックスの範囲チェック
		panic("reflect.Value.Slice3: slice index out of bounds") // 範囲外はパニック
	}

	// Declare slice so that the garbage collector
	// can see the base pointer in it.
	var x []unsafe.Pointer // 新しいスライスを宣言(GCが基底ポインタを認識できるように)

	// Reinterpret as *SliceHeader to edit.
	s := (*SliceHeader)(unsafe.Pointer(&x)) // 新しいスライスのヘッダを操作
	s.Data = uintptr(base) + uintptr(i)*typ.elem.Size() // データポインタを設定
	s.Len = j - i                                       // 長さを設定
	s.Cap = k - i                                       // 容量を設定

	fl := v.flag&flagRO | flagIndir | flag(Slice)<<flagKindShift // フラグを設定
	return Value{typ.common(), unsafe.Pointer(&x), fl}           // 新しいreflect.Valueを返す
}

この関数は、Goの3インデックススライス操作x[i:j:k]をリフレクションで実現します。 まず、入力Valueが配列かスライスかを判断し、それぞれのケースで基になるデータポインタ(base)と元の容量(cap)を取得します。 次に、i, j, kのインデックスが有効な範囲内にあるかを厳密にチェックします。 そして、unsafe.Pointerを使って新しいスライスヘッダを構築し、DataLenCapフィールドを計算された値で設定します。s.Data = uintptr(base) + uintptr(i)*typ.elem.Size()の部分は、基になる配列のi番目の要素から始まるようにデータポインタを調整しています。 最後に、新しいスライスを表すreflect.Valueを生成して返します。var x []unsafe.Pointerは、ガベージコレクタが新しいスライスの基底ポインタを正しく追跡できるようにするための重要なステップです。

Value.Slice の変更 (src/pkg/reflect/value.go)

--- a/src/pkg/reflect/value.go
+++ b/src/pkg/reflect/value.go
@@ -1531,17 +1544,18 @@ func (v Value) SetString(x string) {
 	*(*string)(v.val) = x
 }
 
-// Slice returns a slice of v.
-// It panics if v's Kind is not Array, Slice or String, or if v is an unaddressable array.
-func (v Value) Slice(beg, end int) Value {
+// Slice returns v[i:j].
+// It panics if v's Kind is not Array, Slice or String, or if v is an unaddressable array,
+// or if the indexes are out of bounds.
+func (v Value) Slice(i, j int) Value {
 	var (
 		cap  int
 		typ  *sliceType
 		base unsafe.Pointer
 	)
-	switch k := v.kind(); k {
+	switch kind := v.kind(); kind {
 	default:
-		panic(&ValueError{"reflect.Value.Slice", k})
+		panic(&ValueError{"reflect.Value.Slice", kind})
 
 	case Array:
 		if v.flag&flagAddr == 0 {
@@ -1560,17 +1574,17 @@ func (v Value) Slice(beg, end int) Value {
 
 	case String:
 		s := (*StringHeader)(v.val)
-		if beg < 0 || end < beg || end > s.Len {
+		if i < 0 || j < i || j > s.Len {
 			panic("reflect.Value.Slice: string slice index out of bounds")
 		}
 		var x string
 		val := (*StringHeader)(unsafe.Pointer(&x))
-		val.Data = s.Data + uintptr(beg)
-		val.Len = end - beg
+		val.Data = s.Data + uintptr(i)
+		val.Len = j - i
 		return Value{v.typ, unsafe.Pointer(&x), v.flag}
 	}
 
-	if beg < 0 || end < beg || end > cap {
+	if i < 0 || j < i || j > cap {
 		panic("reflect.Value.Slice: slice index out of bounds")
 	}
 
@@ -1579,9 +1593,56 @@ func (v Value) Slice(beg, end int) Value {
 
 	// Reinterpret as *SliceHeader to edit.
 	s := (*SliceHeader)(unsafe.Pointer(&x))
-	s.Data = uintptr(base) + uintptr(beg)*typ.elem.Size()
-	s.Len = end - beg
-	s.Cap = cap - beg
+	s.Data = uintptr(base) + uintptr(i)*typ.elem.Size()
+	s.Len = j - i
+	s.Cap = cap - i
 
 	fl := v.flag&flagRO | flagIndir | flag(Slice)<<flagKindShift
 	return Value{typ.common(), unsafe.Pointer(&x), fl}
 }

この変更は主に引数名の変更と、それに伴う内部の変数名の更新です。機能的なロジックは変わっていませんが、Go言語の慣習に合わせた引数名にすることで、コードの意図がより明確になっています。

関連リンク

参考にした情報源リンク