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

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

このコミットは、Go言語の標準ライブラリ io/ioutil パッケージ内の ReadFile および ReadAll 関数のドキュメントを更新し、EOF (End-of-File) の振る舞いについて明確化するものです。具体的には、これらの関数がファイルの終端に達した場合でも、成功時には nil エラーを返すという仕様を明記しています。これにより、ユーザーが EOF をエラーとして誤って処理することを防ぎ、より堅牢なコードを書くための指針を提供します。

コミット

commit 2f8e5a5f88b0d744fe0c7c13b53e363d38124d88
Author: Rob Pike <r@golang.org>
Date:   Wed Feb 8 11:40:56 2012 +1100

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

https://github.com/golang/go/commit/2f8e5a5f88b0d744fe0c7c13b53e363d38124d88

元コミット内容

    io/ioutil: document EOF behavior in ReadFile and ReadAll
    
    Fixes #2862.
    
    R=golang-dev, n13m3y3r, iant
    CC=golang-dev
    https://golang.org/cl/5646048

変更の背景

Go言語の io パッケージにおける Reader インターフェースは、データの読み込み操作を抽象化しています。Read メソッドは、読み込むデータがない場合に io.EOF エラーを返します。しかし、io/ioutil パッケージの ReadFileReadAll のような関数は、その名前が示す通り、ファイル全体またはリーダーから利用可能なすべてのデータを読み込むことを目的としています。

このような関数が EOF に到達した際に io.EOF をエラーとして返してしまうと、呼び出し元はそれが「ファイルの終端に達した」という正常な状態なのか、「読み込み中に予期せぬエラーが発生した」という異常な状態なのかを区別するのが難しくなります。特に、ReadFileReadAll は「すべてを読み込む」というタスクを完了した時点で成功とみなされるべきであり、その過程で EOF に到達することは予期された振る舞いです。

このコミットは、このような混乱を避けるために、ReadFileReadAll が成功時には nil エラーを返すことを明示的にドキュメントに追加することで、ユーザーがこれらの関数の EOF 処理について正しく理解できるようにすることを目的としています。これにより、io.EOF をエラーとして扱うべきではないというGoの慣習に沿ったコードの記述が促進されます。

前提知識の解説

io.Reader インターフェース

Go言語の io パッケージは、I/Oプリミティブを提供します。その中でも io.Reader インターフェースは、バイトストリームからの読み込み操作を抽象化する最も基本的なインターフェースの一つです。

type Reader interface {
    Read(p []byte) (n int, err error)
}

Read メソッドは、p に最大 len(p) バイトを読み込み、読み込んだバイト数 n とエラー err を返します。

io.EOF

Read メソッドが io.EOF を返すのは、それ以上読み込むデータがないことを示す場合です。これはエラーというよりは、ストリームの終端に到達したことを示すシグナルとして扱われます。Goの慣習では、Readn > 0err == io.EOF を同時に返すことは許容されており、これは「読み込めるデータはすべて読み込んだが、もうこれ以上データはない」という状態を示します。

エラーハンドリングの慣習

Go言語では、エラーは関数の最後の戻り値として返されるのが一般的です。慣習として、nil はエラーがないことを意味し、非nil の値はエラーが発生したことを意味します。io.EOF は特殊なエラー値であり、通常はストリームの終端を示すために使用され、一般的なエラーとは区別して扱われるべきです。

技術的詳細

io/ioutil パッケージの ReadFileReadAll 関数は、それぞれファイル全体と io.Reader からの全データを読み込むことを目的としています。これらの関数は、内部的に io.ReaderRead メソッドを繰り返し呼び出してデータを読み込みます。

Read メソッドが io.EOF を返した場合、それは「これ以上データがない」ということを意味し、ReadFileReadAll のような「すべてを読み込む」関数にとっては、そのタスクが正常に完了したことを示します。したがって、これらの関数が io.EOF をエラーとして呼び出し元に伝播させるのは不適切です。なぜなら、それは読み込みが成功した結果であり、予期せぬ問題が発生したわけではないからです。

このコミットで追加されたドキュメントは、この重要な振る舞いを明確にしています。

  • ReadAll のドキュメントに追加された説明:

    A successful call returns err == nil, not err == EOF. Because ReadAll is defined to read from src until EOF, it does not treat an EOF from Read as an error to be reported. (成功した呼び出しは err == nil を返し、err == EOF ではありません。ReadAll はソースから EOF まで読み込むように定義されているため、Read からの EOF を報告すべきエラーとして扱いません。)

  • ReadFile のドキュメントに追加された説明:

    A successful call returns err == nil, not err == EOF. Because ReadFile reads the whole file, it does not treat an EOF from Read as an error to be reported. (成功した呼び出しは err == nil を返し、err == EOF ではありません。ReadFile はファイル全体を読み込むため、Read からの EOF を報告すべきエラーとして扱いません。)

これらの説明は、ReadFileReadAllio.EOF を内部的に処理し、読み込みが完了した場合には nil エラーを返すというGoの設計思想と慣習を強調しています。これにより、開発者はこれらの関数からの戻り値をより正確に解釈し、io.EOF を適切に処理することができます。

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

--- a/src/pkg/io/ioutil/ioutil.go
+++ b/src/pkg/io/ioutil/ioutil.go
@@ -34,11 +34,17 @@ func readAll(r io.Reader, capacity int64) (b []byte, err error) {
 }
 
 // ReadAll reads from r until an error or EOF and returns the data it read.
+// A successful call returns err == nil, not err == EOF. Because ReadAll is
+// defined to read from src until EOF, it does not treat an EOF from Read
+// as an error to be reported.
 func ReadAll(r io.Reader) ([]byte, error) {
 	return readAll(r, bytes.MinRead)
 }
 
 // ReadFile reads the file named by filename and returns the contents.
+// A successful call returns err == nil, not err == EOF. Because ReadFile
+// reads the whole file, it does not treat an EOF from Read as an error
+// to be reported.
 func ReadFile(filename string) ([]byte, error) {
 	f, err := os.Open(filename)
 	if err != nil {

コアとなるコードの解説

このコミットは、src/pkg/io/ioutil/ioutil.go ファイルにドキュメントコメントを追加するものです。実際の関数のロジックには変更はありません。

  • ReadAll 関数の定義の上に、以下のコメントが追加されました。

    // A successful call returns err == nil, not err == EOF. Because ReadAll is
    // defined to read from src until EOF, it does not treat an EOF from Read
    // as an error to be reported.
    

    このコメントは、ReadAll がリーダーから EOF に到達するまで読み込むことを目的としているため、Read メソッドが EOF を返しても、それは成功を示すものであり、エラーとして報告すべきではないことを明確にしています。

  • ReadFile 関数の定義の上に、以下のコメントが追加されました。

    // A successful call returns err == nil, not err == EOF. Because ReadFile
    // reads the whole file, it does not treat an EOF from Read as an error
    // to be reported.
    

    同様に、ReadFile もファイル全体を読み込むことを目的としているため、ファイルの終端に達して ReadEOF を返しても、それは成功を示すものであり、エラーとして報告すべきではないことを明記しています。

これらのコメントは、Go言語の io パッケージにおける EOF の慣習的な扱いを強調し、ReadFileReadAll のような「すべてを読み込む」関数が、その目的を達成した場合には nil エラーを返すという期待される振る舞いをユーザーに伝えます。

関連リンク

  • Go issue #2862: コミットメッセージに Fixes #2862 と記載されていますが、現在のGoのGitHubリポジトリでこの番号のIssueを検索しても、このコミットと直接関連するIssueは見つかりませんでした。これは、Issueトラッカーの移行や、古いIssueがアーカイブされたことによるものかもしれません。

参考にした情報源リンク

  • Go言語の io パッケージのドキュメント (一般的な io.Readerio.EOF の振る舞いについて)
  • Go言語のエラーハンドリングに関する一般的な慣習
  • このコミット自体の内容と差分