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

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

このコミットは、Go言語の標準ライブラリであるioパッケージにCopyn関数を追加するものです。Copyn関数は、指定されたバイト数だけReaderからWriterへデータをコピーする機能を提供します。

コミット

commit 79d94d504f8f3e82e994a4f63d37f56cebc6e7cc
Author: Robert Griesemer <gri@golang.org>
Date:   Tue Nov 18 18:08:05 2008 -0800

    Copyn
    
    R=rsc
    DELTA=34  (34 added, 0 deleted, 0 changed)
    OCL=19541
    CL=19545

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

https://github.com/golang/go/commit/79d94d504f8f3e82e994a4f63d37f56cebc6e7cc

元コミット内容

Copyn
    
R=rsc
DELTA=34  (34 added, 0 deleted, 0 changed)
OCL=19541
CL=19545

変更の背景

このコミットは、Go言語の初期開発段階(2008年)に行われたものです。ioパッケージは、Go言語における入出力操作の基本的なインターフェースとユーティリティを提供します。ファイル、ネットワーク接続、メモリバッファなど、様々なデータソースとシンクに対して統一的なI/O操作を行うために不可欠なパッケージです。

Copyn関数は、特定のバイト数だけデータをコピーするという、I/O操作において非常に一般的な要件を満たすために導入されました。例えば、プロトコル処理においてヘッダー部分だけを読み込む場合や、固定長のメッセージを処理する場合などに必要となります。このような基本的なユーティリティ関数を標準ライブラリに含めることで、開発者はより効率的かつ安全にI/O処理を記述できるようになります。

前提知識の解説

Go言語のio.Readerio.Writerインターフェース

Go言語のI/Oシステムは、io.Readerio.Writerという2つのシンプルなインターフェースを中心に構築されています。

  • io.Reader:

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

    Readメソッドは、データをpに読み込み、読み込んだバイト数nとエラーerrを返します。nが0でerrio.EOFの場合、データの終端に達したことを示します。

  • io.Writer:

    type Writer interface {
        Write(p []byte) (n int, err error)
    }
    

    Writeメソッドは、pのデータを書き込み、書き込んだバイト数nとエラーerrを返します。

これらのインターフェースは、様々な具体的なI/O実装(ファイル、ネットワーク接続、メモリバッファなど)を抽象化し、統一的な方法で扱うことを可能にします。これにより、コードの再利用性と柔軟性が大幅に向上します。

Go言語の初期のコンパイラ(6g)とバグ

コミット内のコメント// BUG 6g crashes on non-pointer array slicesは、当時のGo言語のコンパイラである6g(Go 1.5以前のGoコンパイラツールチェーンの一部)に存在した既知のバグを示しています。

Go言語の初期には、gcツールチェーン(6gはx86-64アーキテクチャ向けのコンパイラ)が使用されていました。このバグは、配列のスライスをポインタではない形で扱う際に、コンパイラがクラッシュするというものでした。これは、Go言語がまだ開発の初期段階であり、コンパイラやランタイムが成熟していなかったことを示しています。開発者はこのような既知のバグをコードコメントとして残し、将来の修正や回避策の必要性を示していました。

技術的詳細

Copyn関数は、io.Readerio.Writerインターフェースを利用して、指定されたバイト数nをコピーするロジックを実装しています。

export func Copyn(src Read, dst Write, n int) (c int, err *os.Error) {
	buf := new([]byte, 32*1024);  // BUG 6g crashes on non-pointer array slices
	c = 0;
	for c < n {
		l := n - c;
		if l > len(buf) {
			l = len(buf)
		}
		nr, er := src.Read(buf[0 : l]);
		if nr > 0 {
			nw, ew := dst.Write(buf[0 : nr]);
			if nw != nr || ew != nil {
				c += nw;
				if ew == nil {
					ew = os.EIO
				}
				err = ew;
				break;
			}
			c += nr;
		}
		if er != nil {
			err = er;
			break;
		}
		if nr == 0 {
			break;
		}
	}
	return c, err
}
  1. バッファの初期化: buf := new([]byte, 32*1024) データを一時的に保持するための32KBのバイトスライス(バッファ)を確保しています。この行には、前述の6gコンパイラのバグに関するコメントが付いています。

  2. コピー済みバイト数の初期化: c = 0; これまでにコピーされたバイト数を追跡するためのカウンタcを0で初期化します。

  3. コピーループ: for c < n { ... } コピーされたバイト数cが目標のバイト数nに達するまでループを続けます。

  4. 読み込みサイズ計算: l := n - c; if l > len(buf) { l = len(buf) } 残りのコピーすべきバイト数n - cを計算し、それがバッファのサイズlen(buf)を超える場合は、バッファサイズを上限とします。これにより、一度に読み込むバイト数がバッファに収まるように調整されます。

  5. ソースからの読み込み: nr, er := src.Read(buf[0 : l]); srcio.Reader)からlバイトをbufに読み込みます。nrは実際に読み込まれたバイト数、erは読み込み中に発生したエラーです。

  6. 読み込み成功時の処理: if nr > 0 { ... } もしnrが0より大きい(つまり、データが読み込まれた)場合、以下の処理を行います。

    • デスティネーションへの書き込み: nw, ew := dst.Write(buf[0 : nr]); 読み込んだnrバイトをdstio.Writer)に書き込みます。nwは実際に書き込まれたバイト数、ewは書き込み中に発生したエラーです。

    • 書き込みエラーまたは部分書き込みのチェック: if nw != nr || ew != nil { ... } もし書き込まれたバイト数nwが読み込んだバイト数nrと異なる、または書き込みエラーewが発生した場合、エラー処理を行います。 c += nw; 実際に書き込まれたバイト数だけcを更新します。 if ew == nil { ew = os.EIO } もし書き込みエラーがnil(エラーなし)なのに部分書き込みが発生した場合は、os.EIO(I/Oエラー)を設定します。これは、Writerが要求されたすべてのバイトを書き込めなかった場合に備えるための堅牢なエラーハンドリングです。 err = ew; 発生したエラーをCopyn関数の戻り値errに設定します。 break; ループを終了します。

    • 正常な書き込み: c += nr; 読み込みと書き込みが成功した場合、読み込んだバイト数nrcに加算します。

  7. 読み込みエラーのチェック: if er != nil { ... } 読み込み中にエラーerが発生した場合、そのエラーをCopyn関数の戻り値errに設定し、ループを終了します。

  8. EOFのチェック: if nr == 0 { break; } src.Readが0バイトを返し、かつエラーがnilの場合、それは通常、ソースの終端(EOF)に達したことを意味します。この場合もループを終了します。

  9. 戻り値: return c, err 最終的にコピーされたバイト数cと、発生したエラーerrを返します。

この実装は、効率的なバッファリングと堅牢なエラーハンドリングを組み合わせることで、信頼性の高いバイトコピー機能を提供しています。

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

src/lib/io.goファイルに以下のCopyn関数が追加されました。

// Copies n bytes (or until EOF is reached) from src to dst.
// Returns the number of bytes copied and the error, if any.
export func Copyn(src Read, dst Write, n int) (c int, err *os.Error) {
	buf := new([]byte, 32*1024);  // BUG 6g crashes on non-pointer array slices
	c = 0;
	for c < n {
		l := n - c;
		if l > len(buf) {
			l = len(buf)
		}
		nr, er := src.Read(buf[0 : l]);
		if nr > 0 {
			nw, ew := dst.Write(buf[0 : nr]);
			if nw != nr || ew != nil {
				c += nw;
				if ew == nil {
					ew = os.EIO
				}
				err = ew;
				break;
			}
			c += nr;
		}
		if er != nil {
			err = er;
			break;
		}
		if nr == 0 {
			break;
		}
	}
	return c, err
}

コアとなるコードの解説

上記の「技術的詳細」セクションで、Copyn関数の各行および各ブロックの動作について詳細に解説しました。この関数は、io.Readerio.Writerインターフェースを介して、任意のデータストリーム間で指定されたバイト数を効率的かつ安全にコピーするための基本的なメカニズムを提供します。バッファリング、部分的な読み書きの処理、およびエラー伝播のロジックが組み込まれており、Go言語のI/O設計思想をよく表しています。特に、Go言語の初期段階におけるコンパイラのバグに対するコメントは、当時の開発状況を垣間見ることができます。

関連リンク

参考にした情報源リンク

  • Go言語のioパッケージのソースコード(コミット時点のバージョン): https://github.com/golang/go/blob/79d94d504f8f3e82e994a4f63d37f56cebc6e7cc/src/lib/io.go
  • Go言語の6gコンパイラに関する情報(一般的な情報源):
    • Go言語のコンパイラツールチェーンの歴史に関する記事やドキュメント
    • Go言語の初期のバグトラッカーやメーリングリストのアーカイブ(もし公開されている場合)

注記: 6gコンパイラの「non-pointer array slices」に関する具体的なバグ情報は、現在の公開されている情報源からは特定が困難でした。これは、Go言語が非常に活発に開発されており、古いコンパイラのバグは迅速に修正され、その詳細が一般に公開され続けることが少ないためと考えられます。しかし、コミットメッセージに明記されていることから、当時の開発者にとっては認識されていた重要な問題であったことが伺えます。