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

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

このコミットは、Go言語の標準ライブラリである encoding/binary パッケージにおける Read および Write 関数のデータ型に関する説明を改善することを目的としています。特に、「固定サイズ値 (fixed-size value)」の定義を明確にし、関連する関数のドキュメントをこの新しい定義に合わせて更新しています。また、Size 関数の実装に小さな修正が加えられ、ポインタが渡された場合でも正しく動作するように改善されています。

コミット

commit 59dc21584ace6523007642294c8c38cd7d0819f9
Author: Rob Pike <r@golang.org>
Date:   Fri Feb 10 09:55:48 2012 +1100

    encoding/binary: another attempt to describe the type of Read and Write's data
    
    R=golang-dev, rsc, gri, r
    CC=golang-dev
    https://golang.org/cl/5641062

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

https://github.com/golang/go/commit/59dc21584ace6523007642294c8c38cd7d0819f9

元コミット内容

    encoding/binary: another attempt to describe the type of Read and Write's data
    
    R=golang-dev, rsc, gri, r
    CC=golang-dev
    https://golang.org/cl/5641062

変更の背景

encoding/binary パッケージは、Goのデータ構造とバイトシーケンス間の変換を扱うための重要なパッケージです。Read および Write 関数は、このパッケージの中心的な機能であり、構造化されたバイナリデータを読み書きするために使用されます。これらの関数は interface{} 型の data 引数を受け取りますが、この data 引数にどのような型の値が渡されるべきか、その「デコード可能 (decodable)」または「エンコード可能 (encodable)」な値の定義が、以前のドキュメントでは曖昧であった可能性があります。

このコミットの背景には、Read および Write 関数が処理できるデータ型の正確な定義を明確にし、ユーザーがパッケージをより正確に理解し、誤用を避けるためのドキュメント改善の必要性があったと考えられます。特に、Goの型システムにおける「固定サイズ値」という概念を導入し、それを一貫して使用することで、ドキュメントの整合性と明確性を高めることが意図されています。

また、Size 関数がポインタを受け取った場合に、そのポインタが指す実際の値のサイズを正しく計算できるようにするための修正も、このドキュメントの明確化と合わせて行われています。これは、ReadWrite がポインタを受け取ることを許容しているため、Size も同様にポインタを適切に扱えるべきであるという整合性の観点からも重要です。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念と encoding/binary パッケージの基本的な知識が必要です。

  1. Go言語の型システム: Goは静的型付け言語であり、各変数には特定の型があります。int8, uint8, int16, float32, complex64 などは、それぞれ特定のビット幅を持つ数値型です。
  2. interface{} (空インターフェース): Goにおける interface{} は、任意の型の値を保持できる型です。これは、ReadWrite のように、様々な型のデータを汎用的に扱いたい場合に利用されます。しかし、interface{} を使用する際には、実行時に型アサーションや reflect パッケージを用いて実際の型を検査する必要があります。
  3. encoding/binary パッケージ: このパッケージは、Goのプリミティブ型(整数、浮動小数点数など)やそれらを含む構造体と、バイトシーケンス(バイナリデータ)との間で変換を行う機能を提供します。ネットワークプロトコル、ファイルフォーマット、シリアライゼーションなどで利用されます。
    • ByteOrder: バイトオーダー(エンディアン)を指定するためのインターフェースです。binary.BigEndianbinary.LittleEndian が標準で提供されます。
    • Read(r io.Reader, order ByteOrder, data interface{}) error: io.Reader からバイナリデータを読み込み、指定されたバイトオーダーでデコードし、data に書き込みます。
    • Write(w io.Writer, order ByteOrder, data interface{}) error: data のバイナリ表現を指定されたバイトオーダーでエンコードし、io.Writer に書き込みます。
    • Size(v interface{}) int: v をエンコードした場合に生成されるバイト数を返します。
  4. reflect パッケージ: Goの reflect パッケージは、プログラムの実行時に型情報を検査したり、値を操作したりするための機能を提供します。
    • reflect.ValueOf(v interface{}) Value: 任意のGoの値を reflect.Value 型に変換します。これにより、実行時にその値の型や内容を調べることができます。
    • reflect.Indirect(v reflect.Value) reflect.Value: v がポインタの場合、そのポインタが指す先の値(間接参照された値)を返します。v がポインタでない場合は、v そのものを返します。これは、ポインタの有無にかかわらず、常に基になる値に対して操作を行いたい場合に便利です。
  5. 固定サイズ値 (Fixed-size values): encoding/binary の文脈における「固定サイズ値」とは、そのメモリ上のサイズがコンパイル時に決定され、実行中に変化しない値のことです。これには、int8, uint8, float32 などのプリミティブな数値型、およびそれらの型の要素のみを含む配列や構造体が含まれます。スライスや文字列のように、実行時にサイズが変動する可能性のある型は、通常、固定サイズ値とは見なされません。encoding/binary パッケージは、主にこのような固定サイズ値のバイナリ変換を効率的に行うために設計されています。

技術的詳細

このコミットの技術的な変更点は、主に encoding/binary パッケージのドキュメントの明確化と、Size 関数の挙動の改善にあります。

ドキュメントの変更

最も重要な変更は、パッケージレベルで「固定サイズ値 (fixed-size value)」の明確な定義が追加されたことです。

// Package binary implements translation between
// unsigned integer values and byte sequences
// and the reading and writing of fixed-size values.
// A fixed-size value is either a fixed-size arithmetic
// type (int8, uint8, int16, float32, complex64, ...)
// or an array or struct containing only fixed-size values.
package binary

この定義により、encoding/binary パッケージが扱うデータの種類が明確になりました。具体的には、以下のいずれかの条件を満たす型が「固定サイズ値」と定義されています。

  1. 固定サイズの算術型: int8, uint8, int16, float32, complex64 など、メモリ上のサイズが固定されているプリミティブな数値型。
  2. 固定サイズ値のみを含む配列または構造体: 配列の場合、その要素がすべて固定サイズ値であること。構造体の場合、そのフィールドがすべて固定サイズ値であること。

この新しい定義に基づいて、Read, Write, Size 関数のドキュメントが更新されました。以前は「デコード可能 (decodable)」や「エンコード可能 (encodable)」という用語が使われていましたが、これらが「固定サイズ値」というより具体的な用語に置き換えられました。

Read 関数のドキュメント変更:

  • 変更前: Data must be a pointer to a decodable value or a slice of decodable values.
  • 変更後: Data must be a pointer to a fixed-size value or a slice of fixed-size values.

Write 関数のドキュメント変更:

  • 変更前: Data must be an encodable value or a pointer to an encodable value.
  • 変更後: Data must be a fixed-size value or a slice of fixed-size values, or a pointer to such data.

Size 関数のドキュメント変更:

  • 変更前: Size returns how many bytes Write would generate to encode the value v, assuming the Write would succeed.
  • 変更後: Size returns how many bytes Write would generate to encode the value v, which must be a fixed-size value or a slice of fixed-size values, or a pointer to such data.

これらの変更により、各関数が期待する data 引数の型が、パッケージ全体で一貫した「固定サイズ値」という概念で説明されるようになりました。

Size 関数の実装変更

Size 関数の実装にも重要な変更が加えられました。

  • 変更前: return dataSize(reflect.ValueOf(v))
  • 変更後: return dataSize(reflect.Indirect(reflect.ValueOf(v)))

この変更は、Size 関数にポインタが渡された場合に、そのポインタが指す実際の値のサイズを計算できるようにするためのものです。reflect.ValueOf(v)vreflect.Value を返しますが、v がポインタの場合、それはポインタ自体の reflect.Value になります。reflect.Indirect() を適用することで、v がポインタであればそのポインタが指す先の値の reflect.Value を取得し、ポインタでなければ v そのものの reflect.Value を取得します。これにより、dataSize 関数は常に基になる値に対して動作し、ポインタの有無にかかわらず正しいサイズを返すことが保証されます。

この修正は、Write 関数が「ポインタ」も受け入れるというドキュメントの変更と整合しています。Write がポインタを受け入れてその指す先の値をエンコードするならば、Size も同様にポインタが指す先の値のサイズを計算できるべきです。

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

このコミットにおけるコアとなるコードの変更は、src/pkg/encoding/binary/binary.go ファイル内の Size 関数の実装です。

--- a/src/pkg/encoding/binary/binary.go
+++ b/src/pkg/encoding/binary/binary.go
@@ -253,10 +250,10 @@ func Write(w *Writer, order ByteOrder, data interface{}) error {
 	return err
 }
 
-// Size returns how many bytes Write would generate to encode the value v, assuming
-// the Write would succeed.
+// Size returns how many bytes Write would generate to encode the value v, which
+// must be a fixed-size value or a slice of fixed-size values, or a pointer to such data.
 func Size(v interface{}) int {
-	return dataSize(reflect.ValueOf(v))
+	return dataSize(reflect.Indirect(reflect.ValueOf(v)))
 }
 
 // dataSize returns the number of bytes the actual data represented by v occupies in memory.

コアとなるコードの解説

変更された Size 関数の行は以下の通りです。

return dataSize(reflect.Indirect(reflect.ValueOf(v)))
  1. reflect.ValueOf(v): これは、Size 関数に渡された interface{} 型の引数 vreflect.Value 型に変換します。reflect.Value は、Goの実行時リフレクションシステムにおける値の抽象表現です。
  2. reflect.Indirect(...): この関数は、reflect.Value がポインタを表している場合、そのポインタが指す先の値の reflect.Value を返します。もし reflect.Value がポインタでなければ、reflect.Indirect は元の reflect.Value をそのまま返します。
    • 例: v*int 型のポインタであれば、reflect.Indirectint 型の reflect.Value を返します。
    • 例: vint 型の値であれば、reflect.Indirectint 型の reflect.Value をそのまま返します。
  3. dataSize(...): この関数(コミットには含まれていませんが、encoding/binary パッケージの内部関数)は、reflect.Value が表す値のバイナリサイズを計算します。

この変更により、Size 関数は、v が直接の固定サイズ値であっても、その固定サイズ値へのポインタであっても、常に正しくそのバイナリサイズを計算できるようになりました。これは、encoding/binary パッケージの Read および Write 関数がポインタを受け入れるという仕様と一貫性を持たせるための重要な修正です。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語の encoding/binary および reflect パッケージのソースコード
  • Go言語のコミット履歴とGerritの変更リスト
  • Go言語におけるリフレクションに関する一般的な解説記事