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

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

このコミットは、Go言語の標準ライブラリ src/lib/io.go 内の Copyn 関数におけるコードの簡素化と最適化を目的としています。具体的には、読み込むバイト数を決定するロジックを2行削減し、より簡潔な記述に改善しています。

コミット

commit 60ee27d96cd314ed9be26a105132538f39656c4f
Author: Robert Griesemer <gri@golang.org>
Date:   Wed Nov 19 10:20:52 2008 -0800

    saving 2 lines :-)
    
    R=rsc
    DELTA=5  (0 added, 2 deleted, 3 changed)
    OCL=19562
    CL=19585

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

https://github.com/golang/go/commit/60ee27d96cd314ed9be26a105132538f39656c4f

元コミット内容

saving 2 lines :-)

R=rsc
DELTA=5  (0 added, 2 deleted, 3 changed)
OCL=19562
CL=19585

変更の背景

このコミットの背景は、Go言語の初期開発段階におけるコードベースの継続的な洗練と最適化の一環です。コミットメッセージにある「saving 2 lines :-)」が示す通り、機能的な変更ではなく、既存のロジックをより簡潔かつ効率的に記述することを目指しています。これは、Go言語が目指す「シンプルさ」と「読みやすさ」という設計哲学に合致する変更と言えます。特に、Copyn のような基本的なI/O操作を行う関数は、パフォーマンスとコードの明瞭さが重要であるため、このような小さな改善も積み重ねて行われました。

前提知識の解説

このコミットを理解するためには、以下のGo言語の基本的な概念とI/Oに関する知識が必要です。

  • io.Reader インターフェース: Go言語におけるデータの読み込み元を抽象化するインターフェースです。Read(p []byte) (n int, err error) メソッドを持ち、p に最大 len(p) バイトを読み込み、読み込んだバイト数 n とエラー err を返します。ファイル、ネットワーク接続、メモリ上のデータなど、様々なソースからの読み込みを統一的に扱えます。

  • io.Writer インターフェース: Go言語におけるデータの書き込み先を抽象化するインターフェースです。Write(p []byte) (n int, err error) メソッドを持ち、p のデータを書き込み、書き込んだバイト数 n とエラー err を返します。ファイル、ネットワーク接続、メモリ上のデータなど、様々なシンクへの書き込みを統一的に扱えます。

  • Copyn 関数 (Go言語の初期のI/Oユーティリティ): このコミットで変更されている Copyn 関数は、io.Reader から io.Writer へ指定されたバイト数 n をコピーするユーティリティ関数です。現在のGo標準ライブラリでは io.CopyN として提供されていますが、このコミットはGo言語の非常に初期の段階のものであるため、当時の関数名が Copyn であったと考えられます。

  • スライス (Slice): Go言語の組み込み型で、配列の一部を参照する動的なデータ構造です。buf := new([]byte, 32*1024) のように宣言され、buf[0 : l] のように範囲指定して使用します。

  • 短縮変数宣言 (:=): Go言語で変数を宣言し、初期値を代入する際に使用される簡潔な構文です。l := len(buf) のように、型推論を利用して変数を宣言できます。

  • if ステートメント内の短縮変数宣言: Go言語では、if ステートメントの条件式の前に、その if ブロック内でのみ有効な変数を宣言できます。if d := n - written; d < int64(l) のように使用され、dif ブロック内でのみアクセス可能です。これは、条件判定に必要な一時変数を宣言する際にコードを簡潔にするためのGoらしいイディオムです。

技術的詳細

このコミットの技術的な核心は、Copyn 関数内で一度に読み込むバイト数 l を計算するロジックの改善にあります。Copyn 関数は、指定されたバイト数 n をコピーするために、内部でバッファ buf を使用して繰り返し読み書きを行います。各イテレーションで、src から読み込むバイト数 l は、以下の2つの値の小さい方である必要があります。

  1. バッファの残り容量: len(buf)
  2. コピーすべき残りのバイト数: n - written (ここで written は既にコピーされたバイト数)

つまり、l = min(len(buf), n - written) を計算する必要があります。

変更前のロジック:

var l int;
if n - written > int64(len(buf)) {
    l = len(buf);
} else {
    l = int(n - written);
}

このコードは、n - written がバッファサイズ len(buf) よりも大きいかどうかを比較し、それに応じて llen(buf) または n - written を代入しています。これは一般的な min 関数の実装パターンですが、やや冗長です。

変更後のロジック:

l := len(buf);
if d := n - written; d < int64(l) {
    l = int(d);
}

この新しいロジックは、まず l をバッファの最大サイズ len(buf) で初期化します。次に、if ステートメント内で d という一時変数に n - written (残りのコピーすべきバイト数) を代入し、その d が現在の l (つまり len(buf)) よりも小さい場合にのみ、ld に更新します。

これにより、l は常に len(buf)n - written の小さい方の値を持つことになります。このパターンは、Go言語で min 操作を簡潔に記述する際の一般的なイディオムであり、コードの行数を削減し、可読性を向上させています。

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

src/lib/io.go ファイルの Copyn 関数内の以下の部分が変更されました。

--- a/src/lib/io.go
+++ b/src/lib/io.go
@@ -82,11 +82,9 @@ export func MakeFullReader(fd Read) Read {
 export func Copyn(src Read, dst Write, n int64) (written int64, err *os.Error) {
 	buf := new([]byte, 32*1024);
 	for written < n {
-		var l int;
-		if n - written > int64(len(buf)) {
-			l = len(buf);
-		} else {
-			l = int(n - written);
+		l := len(buf);
+		if d := n - written; d < int64(l) {
+			l = int(d);
 		}
 		nr, er := src.Read(buf[0 : l]);
 		if nr > 0 {

コアとなるコードの解説

変更されたのは、Copyn 関数内の for ループの冒頭部分です。このループは、指定された n バイトをコピーし終えるまで繰り返されます。

変更前:

		var l int; // l を宣言
		if n - written > int64(len(buf)) { // 残りバイト数がバッファサイズより大きいか?
			l = len(buf); // 大きいならバッファサイズ分読み込む
		} else {
			l = int(n - written); // 小さいなら残りバイト数分読み込む
		}

このコードは、l という変数を宣言し、if-else 文を使って、読み込むべきバイト数 (n - written) とバッファのサイズ (len(buf)) のうち、小さい方を l に代入していました。これは論理的には正しいですが、3行を要しています。

変更後:

		l := len(buf); // l をバッファサイズで初期化
		if d := n - written; d < int64(l) { // d (残りバイト数) が現在の l (バッファサイズ) より小さいか?
			l = int(d); // 小さいなら l を d に更新
		}

この新しいコードは、まず l をバッファの最大サイズ (len(buf)) で初期化します。次に、if ステートメントの条件式内で d という新しい変数を宣言し、これにコピーすべき残りのバイト数 (n - written) を代入します。そして、d が現在の l (つまり len(buf)) よりも小さい場合にのみ、l の値を d に更新します。

この変更により、l は常に min(len(buf), n - written) の値を持つことになり、同じロジックを2行少ないコードで実現しています。これはGo言語における簡潔な記述の好例であり、コードの保守性と読みやすさの向上に貢献しています。

関連リンク

参考にした情報源リンク

  • Go言語のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
  • Go言語の初期の設計思想に関する情報 (Go Blogなど): https://go.dev/blog/
  • Go言語における min 関数の実装パターンに関する一般的な情報 (Go言語のイディオム): (特定のURLはありませんが、Goコミュニティで広く知られているパターンです)