[インデックス 1105] ファイルの概要
このコミットは、Go言語の標準ライブラリであるio
パッケージに、io.Read
インターフェースの「フルリーダー」ラッパーを実装するものです。このラッパーは、バッファが完全に満たされるか、0バイトが読み込まれるか、またはエラーが返されることを保証する読み込み動作を提供します。これにより、部分的な読み込み(partial read)の処理を抽象化し、より予測可能なI/O操作を可能にします。
コミット
commit 6ee7fe58087ec5a15bf9ae7717d3fc13bdd407e7
Author: Rob Pike <r@golang.org>
Date: Tue Nov 11 17:28:36 2008 -0800
Implement a "full reader" wrapper for io.Read, guaranteeing that
either the buffer is full, zero bytes were read, or an error is returned.
R=rsc
DELTA=44 (42 added, 0 deleted, 2 changed)
OCL=19027
CL=19047
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6ee7fe58087ec5a15bf9ae7717d3fc13bdd407e7
元コミット内容
io.Read
の「フルリーダー」ラッパーを実装し、バッファが満たされるか、0バイトが読み込まれるか、またはエラーが返されることを保証する。
変更の背景
Go言語のio.Reader
インターフェースのRead
メソッドは、要求されたバイト数よりも少ないバイト数を返すことがあります。これは「部分的な読み込み(partial read)」と呼ばれ、ネットワークソケットからの読み込みなど、データが断続的に到着するようなシナリオでは効率的で正しい動作です。しかし、アプリケーションによっては、指定されたバッファを完全に埋めるまでデータを読み込みたい、あるいはエラーが発生するまで読み込みを続けたいという要件があります。
このような場合、開発者は通常、Read
メソッドをループ内で繰り返し呼び出し、バッファが満たされるか、エラーが発生するまで読み込みを続けるというボイラープレートコードを書く必要がありました。このコミットは、この一般的なパターンを抽象化し、開発者がより簡潔に「フルリード」のセマンティクスを実現できるようにするためのものです。これにより、コードの重複を減らし、I/O操作の堅牢性を向上させることが目的です。
前提知識の解説
io.Reader
インターフェース: Go言語における基本的なI/Oインターフェースの一つです。Read(p []byte) (n int, err error)
メソッドを持ち、データソースからバイトスライスp
にデータを読み込みます。n
は実際に読み込まれたバイト数、err
は読み込み中に発生したエラーを示します。重要なのは、Read
メソッドはlen(p)
バイトを読み込むことを保証せず、0 < n <= len(p)
の範囲で任意のバイト数を返す可能性がある点です。- 部分的な読み込み(Partial Read):
io.Reader
のRead
メソッドが、要求されたバイト数(len(p)
)よりも少ないバイト数(n < len(p)
)を返す現象です。これはエラーではありません。 - EOF (End Of File): データソースの終端に達したことを示す状態です。
io.Reader
のRead
メソッドは、EOFに達した場合、n=0
とerr=io.EOF
(または当時のos.NewError("EOF")
)を返すことが期待されます。ただし、このコミットの時点では、io.EOF
はまだ標準化されておらず、os.NewError("EOF")
が使用されています。 - フルリードの必要性: 特定のプロトコルやファイル形式では、固定長のデータを読み込むことが期待される場合があります。このような場合、部分的な読み込みを自動的に処理し、常に要求されたバイト数を読み込むか、または明確なエラー(EOFを含む)を返すメカニズムが求められます。
技術的詳細
このコミットでは、主に以下の3つの要素が導入されています。
ErrEOF
の定義:os.NewError("EOF")
として、EOF
を示す新しいエラー変数が定義されています。これは、Readn
関数がバッファを完全に満たせないが、それ以上のデータがない場合に返されるエラーとして使用されます。Readn
関数の実装:- この関数は、
io.Read
インターフェースとバイトスライスbuf
を受け取ります。 for n < len(buf)
ループを使用して、buf
が完全に満たされるまでfd.Read
を繰り返し呼び出します。fd.Read
がnn > 0
バイトを読み込んだ場合、n
に加算されます。e != nil
の場合、読み込み中にエラーが発生したことを意味するため、そのエラーを返します。nn <= 0
かつエラーがない場合、これはデータソースの終端に達したか、それ以上読み込むデータがないことを意味します。この場合、ErrEOF
を返します。これは、バッファを完全に満たせなかったが、それ以上のデータがないという特定の状況を示します。
- この関数は、
FullRead
構造体とMakeFullReader
関数:FullRead
は、io.Read
インターフェースをラップするための構造体です。FullRead
のRead
メソッドは、内部的にReadn
関数を呼び出すことで、ラップされたio.Read
の動作を「フルリード」セマンティクスに変換します。MakeFullReader
はコンストラクタ関数であり、既存のio.Read
インターフェースを受け取り、それをFullRead
ラッパーで包んで返します。もし引数が既にFullRead
のインスタンスであれば、二重にラップするのを避けるためにそのまま返します。
これらの要素により、任意のio.Read
インターフェースを、常に要求されたバイト数を読み込むか、または明確なエラー(EOFを含む)を返す「フルリーダー」として利用できるようになります。
コアとなるコードの変更箇所
src/lib/io.go
ファイルに以下の変更が加えられました。
--- a/src/lib/io.go
+++ b/src/lib/io.go
@@ -3,8 +3,13 @@
// license that can be found in the LICENSE file.
package io
-import os "os"
-import syscall "syscall"
+
+import (
+ "os";
+ "syscall";
+)
+
+export var ErrEOF = os.NewError("EOF")
export type Read interface {
Read(p *[]byte) (n int, err *os.Error);
@@ -34,3 +39,40 @@ export func WriteString(w Write, s string) (n int, err *os.Error) {
r, e := w.Write(b[0:len(s)]);
return r, e
}
+
+// Read until buffer is full, EOF, or error
+export func Readn(fd Read, buf *[]byte) (n int, err *os.Error) {
+ n = 0;
+ for n < len(buf) {
+ nn, e := fd.Read(buf[n:len(buf)]);
+ if nn > 0 {
+ n += nn
+ }
+ if e != nil {
+ return n, e
+ }
+ if nn <= 0 {
+ return n, ErrEOF // no error but insufficient data
+ }
+ }
+ return n, nil
+}
+
+// Convert something that implements Read into something
+// whose Reads are always Readn
+type FullRead struct {
+ fd Read;
+}
+
+func (fd *FullRead) Read(p *[]byte) (n int, err *os.Error) {
+ n, err = Readn(fd, p);
+ return n, err
+}
+
+export func MakeFullReader(fd Read) Read {
+ if fr, ok := fd.(*FullRead); ok {
+ // already a FullRead
+ return fd
+ }
+ return &FullRead{fd}
+}
コアとなるコードの解説
-
import
文の変更:- 以前は
import os "os"
とimport syscall "syscall"
のように個別にインポートされていましたが、新しいGoの慣習に合わせて括弧で囲まれたグループインポート形式に変更されました。
- 以前は
-
export var ErrEOF = os.NewError("EOF")
:ErrEOF
という新しいグローバルエラー変数が定義されました。これは、Readn
関数がバッファを完全に満たせないが、それ以上読み込むデータがない場合に返される特定のEOFエラーを示します。
-
export func Readn(fd Read, buf *[]byte) (n int, err *os.Error)
:- この関数は、指定された
Read
インターフェースfd
から、buf
スライスが完全に満たされるまでデータを読み込みます。 n = 0
で読み込んだバイト数を初期化します。for n < len(buf)
ループは、バッファが完全に満たされるまで続きます。fd.Read(buf[n:len(buf)])
で、バッファの残りの部分にデータを読み込もうとします。if nn > 0
の場合、実際に読み込まれたバイト数nn
を合計n
に加算します。if e != nil
の場合、読み込み中にエラーが発生したため、それまでの読み込みバイト数n
とそのエラーe
を返します。if nn <= 0
の場合、これはfd.Read
が0バイトを返したことを意味します。もしエラーがnil
であれば、これはデータソースの終端に達したことを示しますが、バッファはまだ満たされていません。このシナリオでは、ErrEOF
を返します。コメントにあるように「エラーではないがデータが不十分」な状態です。- ループが完了し、
n == len(buf)
となれば、バッファは完全に満たされたため、n
とnil
エラーを返します。
- この関数は、指定された
-
type FullRead struct { fd Read; }
:FullRead
という新しい構造体が定義されました。この構造体は、io.Read
インターフェースをフィールドfd
として保持します。これは、既存のRead
インターフェースをラップするためのものです。
-
func (fd *FullRead) Read(p *[]byte) (n int, err *os.Error)
:FullRead
構造体にRead
メソッドが実装されました。これにより、FullRead
自身もio.Read
インターフェースを満たします。- このメソッドは、単にラップしている
fd
に対してReadn
関数を呼び出し、その結果を返します。これにより、FullRead
のRead
メソッドは常に「フルリード」のセマンティクスを提供します。
-
export func MakeFullReader(fd Read) Read
:MakeFullReader
は、FullRead
のインスタンスを作成するためのファクトリ関数です。- 引数
fd
が既に*FullRead
型であるかどうかを型アサーションif fr, ok := fd.(*FullRead); ok
でチェックします。 - もし既に
FullRead
であれば、二重にラップするのを避けるために、元のfd
をそのまま返します。 - そうでなければ、新しい
FullRead
インスタンスを作成し、引数fd
をそのfd
フィールドに設定して返します。
これらの変更により、GoのI/O操作において、より堅牢で予測可能な「フルリード」の動作を簡単に実現できるようになりました。
関連リンク
- Go言語
io
パッケージ (現在のドキュメント): https://pkg.go.dev/io - Go言語における
io.Reader
の動作に関する議論 (Stack Overflowなど): https://stackoverflow.com/questions/tagged/go+io.reader
参考にした情報源リンク
- コミット情報:
/home/orange/Project/comemo/commit_data/1105.txt
- Go言語の
io
パッケージの基本的な理解 - Go言語における部分的な読み込み(partial read)の概念
- Go言語の初期のコミット履歴と設計思想に関する一般的な知識
- Go言語の
import
文の進化に関する知識