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

[インデックス 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.ReaderReadメソッドが、要求されたバイト数(len(p))よりも少ないバイト数(n < len(p))を返す現象です。これはエラーではありません。
  • EOF (End Of File): データソースの終端に達したことを示す状態です。io.ReaderReadメソッドは、EOFに達した場合、n=0err=io.EOF(または当時のos.NewError("EOF"))を返すことが期待されます。ただし、このコミットの時点では、io.EOFはまだ標準化されておらず、os.NewError("EOF")が使用されています。
  • フルリードの必要性: 特定のプロトコルやファイル形式では、固定長のデータを読み込むことが期待される場合があります。このような場合、部分的な読み込みを自動的に処理し、常に要求されたバイト数を読み込むか、または明確なエラー(EOFを含む)を返すメカニズムが求められます。

技術的詳細

このコミットでは、主に以下の3つの要素が導入されています。

  1. ErrEOFの定義: os.NewError("EOF")として、EOFを示す新しいエラー変数が定義されています。これは、Readn関数がバッファを完全に満たせないが、それ以上のデータがない場合に返されるエラーとして使用されます。
  2. Readn関数の実装:
    • この関数は、io.Readインターフェースとバイトスライスbufを受け取ります。
    • for n < len(buf)ループを使用して、bufが完全に満たされるまでfd.Readを繰り返し呼び出します。
    • fd.Readnn > 0バイトを読み込んだ場合、nに加算されます。
    • e != nilの場合、読み込み中にエラーが発生したことを意味するため、そのエラーを返します。
    • nn <= 0かつエラーがない場合、これはデータソースの終端に達したか、それ以上読み込むデータがないことを意味します。この場合、ErrEOFを返します。これは、バッファを完全に満たせなかったが、それ以上のデータがないという特定の状況を示します。
  3. FullRead構造体とMakeFullReader関数:
    • FullReadは、io.Readインターフェースをラップするための構造体です。
    • FullReadReadメソッドは、内部的に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}
+}

コアとなるコードの解説

  1. import文の変更:

    • 以前はimport os "os"import syscall "syscall"のように個別にインポートされていましたが、新しいGoの慣習に合わせて括弧で囲まれたグループインポート形式に変更されました。
  2. export var ErrEOF = os.NewError("EOF"):

    • ErrEOFという新しいグローバルエラー変数が定義されました。これは、Readn関数がバッファを完全に満たせないが、それ以上読み込むデータがない場合に返される特定のEOFエラーを示します。
  3. 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)となれば、バッファは完全に満たされたため、nnilエラーを返します。
  4. type FullRead struct { fd Read; }:

    • FullReadという新しい構造体が定義されました。この構造体は、io.Readインターフェースをフィールドfdとして保持します。これは、既存のReadインターフェースをラップするためのものです。
  5. func (fd *FullRead) Read(p *[]byte) (n int, err *os.Error):

    • FullRead構造体にReadメソッドが実装されました。これにより、FullRead自身もio.Readインターフェースを満たします。
    • このメソッドは、単にラップしているfdに対してReadn関数を呼び出し、その結果を返します。これにより、FullReadReadメソッドは常に「フルリード」のセマンティクスを提供します。
  6. export func MakeFullReader(fd Read) Read:

    • MakeFullReaderは、FullReadのインスタンスを作成するためのファクトリ関数です。
    • 引数fdが既に*FullRead型であるかどうかを型アサーションif fr, ok := fd.(*FullRead); okでチェックします。
    • もし既にFullReadであれば、二重にラップするのを避けるために、元のfdをそのまま返します。
    • そうでなければ、新しいFullReadインスタンスを作成し、引数fdをそのfdフィールドに設定して返します。

これらの変更により、GoのI/O操作において、より堅牢で予測可能な「フルリード」の動作を簡単に実現できるようになりました。

関連リンク

参考にした情報源リンク

  • コミット情報: /home/orange/Project/comemo/commit_data/1105.txt
  • Go言語のioパッケージの基本的な理解
  • Go言語における部分的な読み込み(partial read)の概念
  • Go言語の初期のコミット履歴と設計思想に関する一般的な知識
  • Go言語のimport文の進化に関する知識