[インデックス 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.Reader
とio.Writer
インターフェース
Go言語のI/Oシステムは、io.Reader
とio.Writer
という2つのシンプルなインターフェースを中心に構築されています。
-
io.Reader
:type Reader interface { Read(p []byte) (n int, err error) }
Read
メソッドは、データをp
に読み込み、読み込んだバイト数n
とエラーerr
を返します。n
が0でerr
がio.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.Reader
とio.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
}
-
バッファの初期化:
buf := new([]byte, 32*1024)
データを一時的に保持するための32KBのバイトスライス(バッファ)を確保しています。この行には、前述の6g
コンパイラのバグに関するコメントが付いています。 -
コピー済みバイト数の初期化:
c = 0;
これまでにコピーされたバイト数を追跡するためのカウンタc
を0で初期化します。 -
コピーループ:
for c < n { ... }
コピーされたバイト数c
が目標のバイト数n
に達するまでループを続けます。 -
読み込みサイズ計算:
l := n - c;
if l > len(buf) { l = len(buf) }
残りのコピーすべきバイト数n - c
を計算し、それがバッファのサイズlen(buf)
を超える場合は、バッファサイズを上限とします。これにより、一度に読み込むバイト数がバッファに収まるように調整されます。 -
ソースからの読み込み:
nr, er := src.Read(buf[0 : l]);
src
(io.Reader
)からl
バイトをbuf
に読み込みます。nr
は実際に読み込まれたバイト数、er
は読み込み中に発生したエラーです。 -
読み込み成功時の処理:
if nr > 0 { ... }
もしnr
が0より大きい(つまり、データが読み込まれた)場合、以下の処理を行います。-
デスティネーションへの書き込み:
nw, ew := dst.Write(buf[0 : nr]);
読み込んだnr
バイトをdst
(io.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;
読み込みと書き込みが成功した場合、読み込んだバイト数nr
をc
に加算します。
-
-
読み込みエラーのチェック:
if er != nil { ... }
読み込み中にエラーer
が発生した場合、そのエラーをCopyn
関数の戻り値err
に設定し、ループを終了します。 -
EOFのチェック:
if nr == 0 { break; }
src.Read
が0バイトを返し、かつエラーがnil
の場合、それは通常、ソースの終端(EOF)に達したことを意味します。この場合もループを終了します。 -
戻り値:
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.Reader
とio.Writer
インターフェースを介して、任意のデータストリーム間で指定されたバイト数を効率的かつ安全にコピーするための基本的なメカニズムを提供します。バッファリング、部分的な読み書きの処理、およびエラー伝播のロジックが組み込まれており、Go言語のI/O設計思想をよく表しています。特に、Go言語の初期段階におけるコンパイラのバグに対するコメントは、当時の開発状況を垣間見ることができます。
関連リンク
- Go言語の
io
パッケージのドキュメント(現在のバージョン): https://pkg.go.dev/io - Go言語の初期のコミット履歴(GitHub): https://github.com/golang/go/commits/master?after=79d94d504f8f3e82e994a4f63d37f56cebc6e7cc+34&branch=master
参考にした情報源リンク
- Go言語の
io
パッケージのソースコード(コミット時点のバージョン): https://github.com/golang/go/blob/79d94d504f8f3e82e994a4f63d37f56cebc6e7cc/src/lib/io.go - Go言語の
6g
コンパイラに関する情報(一般的な情報源):- Go言語のコンパイラツールチェーンの歴史に関する記事やドキュメント
- Go言語の初期のバグトラッカーやメーリングリストのアーカイブ(もし公開されている場合)
注記: 6g
コンパイラの「non-pointer array slices」に関する具体的なバグ情報は、現在の公開されている情報源からは特定が困難でした。これは、Go言語が非常に活発に開発されており、古いコンパイラのバグは迅速に修正され、その詳細が一般に公開され続けることが少ないためと考えられます。しかし、コミットメッセージに明記されていることから、当時の開発者にとっては認識されていた重要な問題であったことが伺えます。