[インデックス 14181] ファイルの概要
このコミットは、Go言語の標準ライブラリである bufio
パッケージのテストコード (bufio_test.go
) における、不要な間接参照(ポインタの使用)を削除する変更です。具体的には、onlyReader
および onlyWriter
というテスト用の構造体のインスタンスを生成する際に、ポインタではなく値型を直接使用するように修正されています。これにより、テストコードの記述がより簡潔になり、わずかながらパフォーマンスの改善や可読性の向上が期待されます。
コミット
commit 0ba5ec53b047376e77418a248233b0645a79a838
Author: Nigel Tao <nigeltao@golang.org>
Date: Sat Oct 20 13:02:29 2012 +1100
bufio: remove a little unnecessary indirection in tests.
R=mchaten, r
CC=golang-dev
https://golang.org/cl/6739045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0ba5ec53b047376e77418a248233b0645a79a838
元コミット内容
bufio: remove a little unnecessary indirection in tests.
R=mchaten, r
CC=golang-dev
https://golang.org/cl/6739045
変更の背景
このコミットの背景には、Go言語におけるインターフェースの実装と、ポインタレシーバおよび値レシーバの使い分けに関するベストプラクティスがあります。bufio
パッケージのテストコードでは、io.Reader
や io.Writer
インターフェースを実装する onlyReader
および onlyWriter
というヘルパ構造体が使用されていました。
元々のコードでは、これらの構造体のインスタンスを生成する際に &onlyReader{...}
や &onlyWriter{...}
のようにポインタを使用していました。しかし、onlyReader
と onlyWriter
の Read
および Write
メソッドは、それぞれ値レシーバ (func (r onlyReader) Read(...)
および func (w onlyWriter) Write(...)
) で定義されていました。
Go言語では、インターフェースを実装する型がポインタレシーバを持つメソッドを定義している場合、そのインターフェースを満たすのはポインタ型のみです。しかし、値レシーバを持つメソッドを定義している場合、そのインターフェースを満たすのは値型とポインタ型の両方です。
このケースでは、onlyReader
と onlyWriter
のメソッドが値レシーバで定義されているため、これらの構造体の値型 (onlyReader
, onlyWriter
) も io.Reader
や io.Writer
インターフェースを直接満たします。したがって、&onlyReader{...}
のようにポインタを介してインスタンスを生成する必要はなく、onlyReader{...}
のように直接値型を生成しても問題ありませんでした。
この変更は、テストコードにおけるわずかながら不要な間接参照を排除し、コードの意図をより明確にするとともに、Goの型システムとインターフェースの振る舞いをより適切に反映させることを目的としています。
前提知識の解説
Go言語のインターフェース
Go言語のインターフェースは、メソッドのシグネチャの集合を定義する型です。型がインターフェースのすべてのメソッドを実装していれば、その型はそのインターフェースを満たします。Goのインターフェースは「暗黙的」に満たされるため、implements
キーワードのような明示的な宣言は不要です。
io.Reader
と io.Writer
インターフェース
Goの標準ライブラリ io
パッケージには、データストリームの読み書きを抽象化するための基本的なインターフェースが定義されています。
io.Reader
: データを読み込むためのインターフェースで、Read(p []byte) (n int, err error)
メソッドを定義します。io.Writer
: データを書き込むためのインターフェースで、Write(p []byte) (n int, err error)
メソッドを定義します。
これらのインターフェースは、ファイル、ネットワーク接続、メモリバッファなど、様々なデータソースやシンクに対して統一的なI/O操作を提供するために広く利用されます。
ポインタレシーバと値レシーバ
Go言語のメソッドは、レシーバ(メソッドが関連付けられる型)を値またはポインタで受け取ることができます。
-
値レシーバ (
func (t MyType) MyMethod(...)
):- メソッドが呼び出される際、レシーバの値のコピーが渡されます。
- メソッド内でレシーバのフィールドを変更しても、元の値には影響しません。
- レシーバが大きな構造体の場合、コピーのコストが発生する可能性があります。
- インターフェースを実装する場合、値型 (
MyType
) とそのポインタ型 (*MyType
) の両方がインターフェースを満たします。
-
ポインタレシーバ (
func (t *MyType) MyMethod(...)
):- メソッドが呼び出される際、レシーバのポインタが渡されます。
- メソッド内でレシーバのフィールドを変更すると、元の値に影響します。
- レシーバが大きな構造体の場合でも、コピーのコストは発生しません。
- インターフェースを実装する場合、そのポインタ型 (
*MyType
) のみがインターフェースを満たします。値型 (MyType
) はインターフェースを満たしません(ただし、Go 1.18以降のジェネリクスではこの限りではありませんが、このコミットの時点では該当しません)。
このコミットの文脈では、onlyReader
と onlyWriter
のメソッドが値レシーバで定義されているため、これらの構造体の値型自体が io.Reader
や io.Writer
インターフェースを満たしていました。
技術的詳細
このコミットの技術的な核心は、Go言語のインターフェースとレシーバの振る舞いを理解し、それに基づいてコードを最適化することにあります。
bufio_test.go
内で定義されている onlyReader
と onlyWriter
構造体は、それぞれ io.Reader
と io.Writer
インターフェースを実装しています。
type onlyReader struct {
r io.Reader
}
// Read メソッドは値レシーバで定義されている
func (r onlyReader) Read(b []byte) (int, error) {
return r.r.Read(b)
}
type onlyWriter struct {
w io.Writer
}
// Write メソッドは値レシーバで定義されている
func (w onlyWriter) Write(b []byte) (int, error) {
return w.w.Write(b)
}
Read
メソッドと Write
メソッドが値レシーバ (func (r onlyReader) Read(...)
および func (w onlyWriter) Write(...)
) で定義されているため、Goのルールにより、onlyReader
型の値も io.Reader
インターフェースを満たします。同様に、onlyWriter
型の値も io.Writer
インターフェースを満たします。
したがって、これらの構造体のインスタンスを生成してインターフェース型として使用する際に、&onlyReader{...}
のようにポインタを取る必要はありませんでした。直接 onlyReader{...}
のように値型を生成しても、インターフェースの要件を満たします。
この変更は、コンパイラが生成するコードに大きな影響を与えるものではありませんが、以下の点でメリットがあります。
- 可読性の向上: 不要な
&
演算子を削除することで、コードがより簡潔になり、onlyReader
やonlyWriter
が値型として扱われていることが明確になります。 - 意図の明確化:
onlyReader
やonlyWriter
が内部状態を変更しない(または変更する必要がない)ことを示唆し、値セマンティクスが適切であることを強調します。 - わずかなパフォーマンス改善(理論上): ポインタを介した間接参照が一つ減るため、ごくわずかながら実行時のオーバーヘッドが削減される可能性があります。ただし、現代のコンパイラはこのような最適化を自動で行うことが多いため、実測でのパフォーマンス差はほとんどないでしょう。
このコミットは、Go言語のイディオムに沿ったコードスタイルへの改善と見なすことができます。
コアとなるコードの変更箇所
変更は src/pkg/bufio/bufio_test.go
ファイルのみで行われています。
具体的には、onlyReader
および onlyWriter
のインスタンスを生成する箇所で、&
演算子(アドレス演算子)が削除されています。
--- a/src/pkg/bufio/bufio_test.go
+++ b/src/pkg/bufio/bufio_test.go
@@ -779,7 +779,7 @@ func createTestInput(n int) []byte {
func TestReaderWriteTo(t *testing.T) {
input := createTestInput(8192)
- r := NewReader(&onlyReader{bytes.NewBuffer(input)})
+ r := NewReader(onlyReader{bytes.NewBuffer(input)})
w := new(bytes.Buffer)
if n, err := r.WriteTo(w); err != nil || n != int64(len(input)) {
t.Fatalf("r.WriteTo(w) = %d, %v, want %d, nil", n, err, len(input))
@@ -824,7 +824,7 @@ func TestReaderWriteToErrors(t *testing.T) {
func TestWriterReadFrom(t *testing.T) {
ws := []func(io.Writer) io.Writer{
- func(w io.Writer) io.Writer { return &onlyWriter{w} },
+ func(w io.Writer) io.Writer { return onlyWriter{w} },
func(w io.Writer) io.Writer { return w },
}
@@ -896,11 +896,11 @@ func TestWriterReadFromCounts(t *testing.T) {
if w0 != 0 {
t.Fatalf("write 1200 'x's: got %d writes, want 0", w0)
}
- io.Copy(b0, &onlyReader{strings.NewReader(strings.Repeat("x", 30))})
+ io.Copy(b0, onlyReader{strings.NewReader(strings.Repeat("x", 30))})
if w0 != 0 {
t.Fatalf("write 1230 'x's: got %d writes, want 0", w0)
}
- io.Copy(b0, &onlyReader{strings.NewReader(strings.Repeat("x", 9))})
+ io.Copy(b0, onlyReader{strings.NewReader(strings.Repeat("x", 9))})
if w0 != 1 {
t.Fatalf("write 1239 'x's: got %d writes, want 1", w0)
}
@@ -916,11 +916,11 @@ func TestWriterReadFromCounts(t *testing.T) {
if w1 != 1 {
t.Fatalf("write 1200 + 89 'x's: got %d writes, want 1", w1)
}
- io.Copy(b1, &onlyReader{strings.NewReader(strings.Repeat("x", 700))})
+ io.Copy(b1, onlyReader{strings.NewReader(strings.Repeat("x", 700))})
if w1 != 1 {
t.Fatalf("write 1200 + 789 'x's: got %d writes, want 1", w1)
}
- io.Copy(b1, &onlyReader{strings.NewReader(strings.Repeat("x", 600))})
+ io.Copy(b1, onlyReader{strings.NewReader(strings.Repeat("x", 600))})
if w1 != 2 {
t.Fatalf("write 1200 + 1389 'x's: got %d writes, want 2", w1)
}
@@ -944,7 +944,7 @@ type onlyReader struct {
r io.Reader
}
-func (r *onlyReader) Read(b []byte) (int, error) {
+func (r onlyReader) Read(b []byte) (int, error) {
return r.r.Read(b)
}
@@ -953,7 +953,7 @@ type onlyWriter struct {
w io.Writer
}
-func (w *onlyWriter) Write(b []byte) (int, error) {
+func (w onlyWriter) Write(b []byte) (int, error) {
return w.w.Write(b)
}
@@ -962,7 +962,7 @@ func BenchmarkReaderCopyOptimal(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
src := NewReader(bytes.NewBuffer(make([]byte, 8192)))
- dst := &onlyWriter{new(bytes.Buffer)}
+ dst := onlyWriter{new(bytes.Buffer)}
b.StartTimer()
io.Copy(dst, src)
}
@@ -972,8 +972,8 @@ func BenchmarkReaderCopyUnoptimal(b *testing.B) {
// Unoptimal case is where the underlying reader doesn't implement io.WriterTo
for i := 0; i < b.N; i++ {
b.StopTimer()
- src := NewReader(&onlyReader{bytes.NewBuffer(make([]byte, 8192))})
- dst := &onlyWriter{new(bytes.Buffer)}
+ src := NewReader(onlyReader{bytes.NewBuffer(make([]byte, 8192))})
+ dst := onlyWriter{new(bytes.Buffer)}
b.StartTimer()
io.Copy(dst, src)
}
@@ -982,8 +982,8 @@ func BenchmarkReaderCopyNoWriteTo(b *testing.B) {
func BenchmarkReaderCopyNoWriteTo(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
- src := &onlyReader{NewReader(bytes.NewBuffer(make([]byte, 8192)))}\n
- dst := &onlyWriter{new(bytes.Buffer)}\n
+ src := onlyReader{NewReader(bytes.NewBuffer(make([]byte, 8192)))}
+ dst := onlyWriter{new(bytes.Buffer)}
b.StartTimer()
io.Copy(dst, src)
}
@@ -993,7 +993,7 @@ func BenchmarkWriterCopyOptimal(b *testing.B) {
// Optimal case is where the underlying writer implements io.ReaderFrom
for i := 0; i < b.N; i++ {
b.StopTimer()
- src := &onlyReader{bytes.NewBuffer(make([]byte, 8192))}\n
+ src := onlyReader{bytes.NewBuffer(make([]byte, 8192))}
dst := NewWriter(new(bytes.Buffer))
b.StartTimer()
io.Copy(dst, src)
@@ -1003,8 +1003,8 @@ func BenchmarkWriterCopyUnoptimal(b *testing.B) {
func BenchmarkWriterCopyUnoptimal(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
- src := &onlyReader{bytes.NewBuffer(make([]byte, 8192))}\n
- dst := NewWriter(&onlyWriter{new(bytes.Buffer)})\n
+ src := onlyReader{bytes.NewBuffer(make([]byte, 8192))}
+ dst := NewWriter(onlyWriter{new(bytes.Buffer)})
b.StartTimer()
io.Copy(dst, src)
}
@@ -1013,8 +1013,8 @@ func BenchmarkWriterCopyNoReadFrom(b *testing.B) {
func BenchmarkWriterCopyNoReadFrom(b *testing.B) {
for i := 0; i < b.N; i++ {
b.StopTimer()
- src := &onlyReader{bytes.NewBuffer(make([]byte, 8192))}\n
- dst := &onlyWriter{NewWriter(new(bytes.Buffer))}\n
+ src := onlyReader{bytes.NewBuffer(make([]byte, 8192))}
+ dst := onlyWriter{NewWriter(new(bytes.Buffer))}
b.StartTimer()
io.Copy(dst, src)
}
コアとなるコードの解説
変更の核心は、onlyReader
と onlyWriter
の構造体リテラルを初期化する際に、ポインタ (&
) を使用しないようにした点です。
変更前:
r := NewReader(&onlyReader{bytes.NewBuffer(input)})
// ...
func(w io.Writer) io.Writer { return &onlyWriter{w} },
// ...
io.Copy(b0, &onlyReader{strings.NewReader(strings.Repeat("x", 30))})
// ...
func (r *onlyReader) Read(b []byte) (int, error) { // ポインタレシーバ
return r.r.Read(b)
}
// ...
func (w *onlyWriter) Write(b []byte) (int, error) { // ポインタレシーバ
return w.w.Write(b)
}
変更後:
r := NewReader(onlyReader{bytes.NewBuffer(input)})
// ...
func(w io.Writer) io.Writer { return onlyWriter{w} },
// ...
io.Copy(b0, onlyReader{strings.NewReader(strings.Repeat("x", 30))})
// ...
func (r onlyReader) Read(b []byte) (int, error) { // 値レシーバ
return r.r.Read(b)
}
// ...
func (w onlyWriter) Write(b []byte) (int, error) { // 値レシーバ
return w.w.Write(b)
}
注目すべきは、onlyReader
と onlyWriter
の Read
および Write
メソッドのレシーバが、このコミットでポインタレシーバから値レシーバに変更されている点です。
- 変更前:
func (r *onlyReader) Read(...)
およびfunc (w *onlyWriter) Write(...)
- 変更後:
func (r onlyReader) Read(...)
およびfunc (w onlyWriter) Write(...)
このレシーバの変更が、インスタンス生成時の &
演算子削除を可能にしています。
Go言語のインターフェースのルールでは、メソッドがポインタレシーバで定義されている場合、そのインターフェースを満たすのはポインタ型のみです。しかし、メソッドが値レシーバで定義されている場合、そのインターフェースを満たすのは値型とポインタ型の両方です。
したがって、onlyReader
と onlyWriter
の Read
/Write
メソッドが値レシーバになったことで、onlyReader{...}
や onlyWriter{...}
といった値型そのものが io.Reader
や io.Writer
インターフェースを満たすようになりました。これにより、不要なポインタの生成を避けることができ、コードがより直接的で簡潔になっています。
これは、Goのイディオムに沿った改善であり、テストコードの可読性と保守性を向上させるための細かな調整です。
関連リンク
- Go言語の
bufio
パッケージ: https://pkg.go.dev/bufio - Go言語の
io
パッケージ: https://pkg.go.dev/io - Go言語におけるメソッドとインターフェースに関する公式ドキュメント(A Tour of Go - Methods and interfaces): https://go.dev/tour/methods/9
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード (
src/pkg/bufio/bufio_test.go
) - Go言語におけるポインタレシーバと値レシーバに関する一般的な解説記事 (例: https://go.dev/doc/effective_go#pointers_vs_values)
- Go言語のインターフェースに関する一般的な解説記事 (例: https://go.dev/doc/effective_go#interfaces_and_other_types)