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

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

このコミットは、Go言語の標準ライブラリであるioパッケージにPipe機能を追加し、同時に既存のコードベースにおける命名規則(特にアンダースコアの使用)の整理を行っています。

主な変更ファイルは以下の通りです。

  • src/lib/Makefile: ビルド設定の更新。ioパッケージにsyncパッケージの依存関係を追加し、pipe.goのコンパイルを含めます。また、timeパッケージのビルドにもsleep.goを含めるよう変更されています。
  • src/lib/io/Makefile: ioパッケージのビルド設定で、新しく追加されるpipe.goをコンパイル対象に含めるよう変更されています。
  • src/lib/io/io.go: ioパッケージの既存のインターフェース定義が整理され、CloseReadCloseWriteCloseといった新しいインターフェースが追加されています。また、内部的な構造体や関数の命名が、Goの慣習に合わせて先頭のアンダースコアが削除されています(例: _FullReadからfullReadMake_FullReaderからMakeFullReader)。
  • src/lib/io/pipe.go: 新規追加ファイルio.Pipe機能の核心となる実装が含まれています。pipe構造体、pipeRead構造体、pipeWrite構造体、そしてPipe()関数が定義されており、goroutineとチャネルを用いた同期的なインメモリパイプが提供されます。
  • src/lib/io/pipe_test.go: 新規追加ファイルio.Pipeの機能が正しく動作することを検証するための単体テストが含まれています。様々な読み書きのシナリオ、クローズ時の挙動などがテストされています。
  • src/lib/time/Makefile: timeパッケージのビルド設定が更新され、sleep.goがコンパイル対象に追加されています。
  • src/lib/time/tick.go: timeパッケージ内のticker関数において、時間待機の実装がsyscall.Syscall6を用いた低レベルなシステムコールから、新しく追加されるtime.Sleep関数へと変更されています。これにより、より高レベルでポータブルな時間待機が可能になります。
  • src/lib/time/time.go: timeパッケージ内のコメントで「GMT」が「UTC」に修正されています。また、io.goと同様に、内部的な定数や関数の命名から先頭のアンダースコアが削除されています(例: _SecondsPerDayからsecondsPerDay_Copyからcopy)。
  • src/run.bash: テスト実行スクリプトにlib/ioが追加され、ioパッケージのテストが実行されるようになっています。

コミット

io.Pipe

assorted underscore cleanup

R=r
DELTA=488  (410 added, 3 deleted, 75 changed)
OCL=25070
CL=25070

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

https://github.com/golang/go/commit/78906c38367d16b5c189163d6f6d60b2e99370f3

元コミット内容

io.Pipe

assorted underscore cleanup

R=r
DELTA=488  (410 added, 3 deleted, 75 changed)
OCL=25070
CL=25070

変更の背景

このコミットは、Go言語の初期段階(2009年)に行われたもので、Goの標準ライブラリの基盤を構築する上で重要な役割を果たしています。

  1. io.Pipeの導入: Go言語はgoroutineとチャネルによる並行処理を強力にサポートしています。プログラム内でデータをストリームとして扱う際、あるgoroutineがデータを生成し(io.Writerとして)、別のgoroutineがそのデータを消費する(io.Readerとして)というパターンは非常に一般的です。しかし、これら二つの異なるインターフェースを持つコンポーネントを直接接続するメカニズムが不足していました。io.Pipeは、このようなシナリオにおいて、io.Writerio.Readerのインターフェースを介して、goroutine間で同期的にデータをやり取りするためのシンプルなインメモリパイプを提供することを目的として導入されました。これにより、例えば、データを圧縮するgoroutineと、その圧縮されたデータをネットワークに送信するgoroutineを容易に連結できるようになります。Unixのパイプ(|)に似た概念をGoのプロセス内並行処理に持ち込むものです。

  2. アンダースコア命名規則の整理: Go言語の設計哲学の一つに「シンプルさと明瞭さ」があります。初期のGoコードベースでは、内部的な(エクスポートされない)識別子にアンダースコアをプレフィックスとして付ける慣習が見られました(例: _FullRead)。しかし、Goの公式な命名規則では、エクスポートされない識別子は小文字で始まることになっており、アンダースコアをプレフィックスとして使用することは推奨されていません。このコミットは、このような初期の慣習をGoの標準的な命名規則に合わせるためのコードベース全体のクリーンアップの一環として行われました。これにより、コードの一貫性が向上し、可読性が高まります。

  3. timeパッケージの改善: timeパッケージにおけるsyscall.Syscall6からtime.Sleepへの変更は、プラットフォーム依存の低レベルなシステムコールを抽象化し、よりポータブルで使いやすい高レベルなAPIに置き換えるというGoの標準ライブラリ開発の方向性を示しています。また、時間に関するコメントの「GMT」から「UTC」への変更は、より正確で国際的な標準への準拠を目指す意図があります。

これらの変更は、Go言語がその設計原則に基づき、堅牢で使いやすい標準ライブラリを構築していく過程における重要なステップと言えます。

前提知識の解説

1. Goのインターフェース

Go言語のインターフェースは、メソッドのシグネチャの集合を定義する型です。Goのインターフェースは「暗黙的」に実装されます。つまり、ある型がインターフェースで定義されたすべてのメソッドを実装していれば、その型はそのインターフェースを実装しているとみなされます。

  • io.Reader: データを読み込むためのインターフェース。

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

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

  • io.Writer: データを書き込むためのインターフェース。

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

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

  • io.Closer: リソースをクローズするためのインターフェース。

    type Closer interface {
        Close() error
    }
    

    Closeメソッドは、リソースを閉じ、エラーerrを返します。

このコミットでは、これらの基本的なインターフェースを組み合わせた新しいインターフェース(ReadClose, WriteClose, ReadWriteClose)が導入され、より具体的な用途に対応できるようになっています。

2. GoのGoroutineとチャネル

Go言語は、並行処理をサポートするために「goroutine」と「チャネル」というプリミティブを提供します。

  • Goroutine: Goランタイムによって管理される軽量なスレッドのようなものです。goキーワードを使って関数呼び出しの前に置くことで、その関数を新しいgoroutineで実行できます。非常に低コストで生成でき、数千、数万のgoroutineを同時に実行することが可能です。
  • チャネル (Channel): goroutine間で値を安全に送受信するための通信メカニズムです。チャネルは型付けされており、特定の型の値のみを送受信できます。チャネルへの送信操作と受信操作は、デフォルトで同期的に行われます(バッファなしチャネルの場合)。つまり、送信側は受信側が値を受け取るまでブロックし、受信側は送信側が値を送るまでブロックします。これにより、共有メモリを直接操作する際によく発生するデータ競合の問題を回避できます。

io.Pipeの実装では、このチャネルがio.Writerio.Readerの間でデータをやり取りするための主要な手段として利用されています。

3. sync.Mutex (排他制御)

sync.Mutexは、Goの標準ライブラリsyncパッケージで提供される相互排他ロック(ミューテックス)です。複数のgoroutineが共有リソースに同時にアクセスするのを防ぎ、データ競合を回避するために使用されます。

  • Lock(): ロックを取得します。既にロックが取得されている場合は、ロックが解放されるまでブロックします。
  • Unlock(): ロックを解放します。

io.Pipeの実装では、pipeReadpipeWrite構造体がそれぞれsync.Mutexを保持し、ReadおよびWrite操作が同時に実行されないように保護しています。これにより、パイプの内部状態の一貫性が保たれます。

4. パイプの概念

「パイプ」は、一方のプロセス(またはGoの場合はgoroutine)の出力が、もう一方のプロセス(またはgoroutine)の入力に直接接続される通信メカニズムです。Unix系OSでは、コマンドラインで|(パイプ)を使ってプログラムの出力を別のプログラムの入力に渡すことができます(例: ls -l | grep .txt)。

io.Pipeは、この概念をGoプログラムのメモリ内で実現します。Pipe()関数は、io.Readerio.Writerのペアを返し、Writerに書き込まれたデータがReaderから読み出せるようになります。これは、異なる処理ステージを連結する際に非常に便利です。

5. Goの命名規則 (アンダースコアの慣習)

Go言語では、識別子の最初の文字が大文字か小文字かによって、その識別子がエクスポートされる(パッケージ外からアクセス可能)か、エクスポートされない(パッケージ内でのみアクセス可能)かが決まります。

  • 大文字で始まる識別子: エクスポートされます(Public)。
  • 小文字で始まる識別子: エクスポートされません(private)。

初期のGoコードベースでは、内部的な識別子に_(アンダースコア)をプレフィックスとして付ける慣習が一部で見られましたが、これはGoの公式な命名規則とは異なります。Goでは、エクスポートされない識別子には単に小文字で始まる名前を使用し、アンダースコアをプレフィックスとして使用することは推奨されていません。このコミットは、この慣習を修正し、Goの標準的な命名規則に準拠させるためのものです。

技術的詳細

io.Pipeの設計と実装

io.Pipeは、Goのgoroutineとチャネルを基盤とした同期的なインメモリパイプを提供します。これは、io.Writerインターフェースを実装する側がデータを書き込み、io.Readerインターフェースを実装する側がそのデータを読み取ることを可能にします。

主要な構造体とチャネル:

  • pipe構造体: パイプの共有状態を管理する中心的な構造体です。

    type pipe struct {
        rclosed bool        // Read end closed?
        wclosed bool        // Write end closed?
        wpend []byte        // Written data waiting to be read.
        wtot int         // Bytes consumed so far in current write.
        cr chan []byte      // Write sends data here...
        cw chan pipeReturn  // ... and reads the n, err back from here.
    }
    
    • rclosed, wclosed: それぞれ読み込み側、書き込み側がクローズされたかどうかを示すフラグ。
    • wpend: 書き込み側から送られてきたが、まだ読み込み側が完全に消費していないデータバッファ。
    • wtot: 現在の書き込み操作で、読み込み側が既に消費したバイト数。
    • cr chan []byte: 書き込み側がデータを送信するためのチャネル。読み込み側はこのチャネルからデータを受け取ります。バッファサイズは1で、これにより書き込み操作は読み込み側がデータを受け取るまでブロックされます(同期的な挙動)。
    • cw chan pipeReturn: 読み込み側が書き込み操作の結果(読み込んだバイト数とエラー)を書き込み側に返すためのチャネル。書き込み側はこのチャネルから結果を受け取るまでブロックされます。バッファサイズは1。
  • pipeReturn構造体: cwチャネルでやり取りされる、書き込み操作の結果を保持するヘルパー構造体。

    type pipeReturn struct {
        n int
        err *os.Error
    }
    
  • pipeRead構造体: パイプの読み込み側を表す構造体。io.Readerio.Closerインターフェースを実装します。

    type pipeRead struct {
        lock sync.Mutex
        p *pipe
    }
    
    • lock: 読み込み操作の排他制御のためのミューテックス。
    • p: 共有のpipe構造体へのポインタ。
  • pipeWrite構造体: パイプの書き込み側を表す構造体。io.Writerio.Closerインターフェースを実装します。

    type pipeWrite struct {
        lock sync.Mutex
        p *pipe
    }
    
    • lock: 書き込み操作の排他制御のためのミューテックス。
    • p: 共有のpipe構造体へのポインタ。

Pipe()関数の動作:

Pipe()関数は、新しいpipe構造体を初期化し、そのcrcwチャネルを作成します。そして、そのpipe構造体へのポインタを持つpipeReadpipeWriteのインスタンスを生成し、それぞれio.ReadCloseio.WriteCloseインターフェースとして返します。

func Pipe() (io.ReadClose, io.WriteClose) {
    p := new(pipe)
    p.cr = make(chan []byte, 1) // バッファサイズ1のチャネル
    p.cw = make(chan pipeReturn, 1) // バッファサイズ1のチャネル
    r := new(pipeRead)
    r.p = p
    w := new(pipeWrite)
    w.p = p
    return r, w
}

pipe.Read()pipe.Write()の同期メカニズム:

  • pipe.Write(data []byte):

    1. 書き込み側がクローズされているか、読み込み側がクローズされているかを確認し、エラーを返します。
    2. p.cr <- dataによって、書き込むデータをcrチャネルに送信します。この操作は、読み込み側がcrチャネルからデータを受け取るまでブロックされます。
    3. res := <-p.cwによって、読み込み側からの処理結果をcwチャネルから受け取ります。この操作は、読み込み側が処理を完了し、結果をcwチャネルに送信するまでブロックされます。
    4. 受け取った結果(書き込んだバイト数とエラー)を返します。
  • pipe.Read(data []byte):

    1. 読み込み側がクローズされているかを確認し、エラーを返します。
    2. p.wpend == nilの場合(つまり、まだ読み込むべきデータがない場合)、p.wpend = <-p.crによってcrチャネルから新しいデータブロックを受け取ります。この操作は、書き込み側がcrチャネルにデータを送信するまでブロックされます。
    3. 受け取ったデータブロック(p.wpend)から、要求されたバイト数(len(data)またはlen(p.wpend)の小さい方)をdataバッファにコピーします。
    4. コピーしたバイト数だけp.wpendをスライスし、p.wtotを更新します。
    5. len(p.wpend) == 0の場合(つまり、現在のデータブロックをすべて読み込んだ場合)、p.cw <- pipeReturn(p.wtot, nil)によって、読み込んだバイト数とエラー(なし)をcwチャネルに送信します。これにより、書き込み側がブロック解除されます。
    6. 読み込んだバイト数を返します。

このチャネルを介した同期的なやり取りにより、書き込み側は読み込み側がデータを消費するまで待機し、読み込み側は書き込み側がデータを生成するまで待機するという、効率的かつ安全なデータフローが実現されます。

CloseReader()CloseWriter()の挙動:

  • pipe.CloseReader(): 読み込み側をクローズします。
    • p.rclosed = trueを設定し、今後の読み込み操作を無効にします。
    • もし書き込み側がまだクローズされていなければ、現在の書き込み操作を中断するためにp.cw <- pipeReturn(p.wtot, os.EPIPE)を送信します。これにより、書き込み側はos.EPIPE(Broken pipe)エラーを受け取ってブロック解除されます。
  • pipe.CloseWriter(): 書き込み側をクローズします。
    • p.wclosed = trueを設定し、今後の書き込み操作を無効にします。
    • もし読み込み側がまだクローズされていなければ、p.cr <- nilを送信します。これにより、読み込み側はnilデータを受け取り、EOF(End Of File)として扱われます。

pipeReadpipeWriteClose()メソッドは、それぞれ対応するpipeCloseReader()CloseWriter()を呼び出します。また、finish()メソッドも定義されており、これはガベージコレクション時に自動的にClose()を呼び出すためのものです(Goの初期のファイナライザの概念)。

アンダースコア命名規則の整理

このコミットでは、Goの命名規則に準拠するため、内部的な識別子から先頭のアンダースコアが削除されています。

src/lib/io/io.goの例:

  • type _FullRead structtype fullRead struct に変更。
  • func Make_FullReaderfunc MakeFullReader に変更。

src/lib/time/time.goの例:

  • const _SecondsPerDayconst secondsPerDay に変更。
  • var _LongDayNamesvar longDayNames に変更。
  • func _Copyfunc copy に変更。
  • func _Decimalfunc decimal に変更。
  • func _AddStringfunc addString に変更。
  • func _Formatfunc format に変更。

これらの変更は機能的な影響はなく、コードのスタイルと一貫性を向上させるためのものです。Goの慣習では、パッケージ外に公開しない(エクスポートしない)識別子は小文字で始めることになっており、アンダースコアをプレフィックスとして使用する必要はありません。

timeパッケージのsyscall.Syscall6からtime.Sleepへの変更

src/lib/time/tick.goでは、ticker goroutineが次のティックまで待機する方法が変更されました。

変更前:

        syscall.Nstotimeval(when - now, &tv);
        syscall.Syscall6(syscall.SYS_SELECT, 0, 0, 0, 0, int64(uintptr(unsafe.Pointer(&tv))), 0);

これは、selectシステムコールを使用して指定された時間だけブロックする、低レベルでプラットフォーム依存の実装でした。

変更後:

        time.Sleep(when - now);

新しく導入されたtime.Sleep関数を使用することで、より高レベルでポータブルな方法で指定された期間だけgoroutineをスリープさせることができます。これは、Goの標準ライブラリが抽象化レイヤーを提供し、ユーザーが低レベルなシステムコールを直接扱う必要がないように進化していることを示しています。

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

src/lib/io/io.go (インターフェースの追加と命名変更)

--- a/src/lib/io/io.go
+++ b/src/lib/io/io.go
@@ -19,18 +19,28 @@ type Write interface {
 	Write(p []byte) (n int, err *os.Error);
 }
 
+type Close interface {
+	Close() *os.Error;
+}
+
 type ReadWrite interface {
 	Read(p []byte) (n int, err *os.Error);
 	Write(p []byte) (n int, err *os.Error);
 }
 
-type ReadWriteClose interface {
+type ReadClose interface {
 	Read(p []byte) (n int, err *os.Error);
+	Close() *os.Error;
+}
+
+type WriteClose interface {
 	Write(p []byte) (n int, err *os.Error);
 	Close() *os.Error;
 }
 
-type Close interface {
+type ReadWriteClose interface {
+	Read(p []byte) (n int, err *os.Error);
+	Write(p []byte) (n int, err *os.Error);
 	Close() *os.Error;
 }
 
@@ -69,21 +79,21 @@ func Readn(fd Read, buf []byte) (n int, err *os.Error) {
 
 // Convert something that implements Read into something
 // whose Reads are always Readn
-type _FullRead struct {
+type fullRead struct {
 	fd	Read;
 }
 
-func (fd *_FullRead) Read(p []byte) (n int, err *os.Error) {
+func (fd *fullRead) Read(p []byte) (n int, err *os.Error) {
 	n, err = Readn(fd.fd, p);
 	return n, err
 }
 
-func Make_FullReader(fd Read) Read {\n-\tif fr, ok := fd.(*_FullRead); ok {\n-\t\t// already a _FullRead\n+func MakeFullReader(fd Read) Read {
+	if fr, ok := fd.(*fullRead); ok {
+		// already a fullRead
 		return fd
 	}
-\treturn &_FullRead(fd)\n+\treturn &fullRead(fd)
 }
 
 // Copies n bytes (or until EOF is reached) from src to dst.

src/lib/io/pipe.go (新規追加ファイル - io.Pipeの実装)

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Pipe adapter to connect code expecting an io.Read
// with code expecting an io.Write.

package io

import (
	"io"
	"os"
	"sync"
)

type pipeReturn struct {
	n int
	err *os.Error
}

// Shared pipe structure.
type pipe struct {
	rclosed bool        // Read end closed?
	wclosed bool        // Write end closed?
	wpend []byte        // Written data waiting to be read.
	wtot int         // Bytes consumed so far in current write.
	cr chan []byte      // Write sends data here...
	cw chan pipeReturn  // ... and reads the n, err back from here.
}

func (p *pipe) Read(data []byte) (n int, err *os.Error) {
	if p == nil || p.rclosed {
		return 0, os.EINVAL
	}

	// Wait for next write block if necessary.
	if p.wpend == nil {
		if !p.wclosed {
			p.wpend = <-p.cr
		}
		if p.wpend == nil {
			return 0, nil
		}
		p.wtot = 0
	}

	// Read from current write block.
	n = len(data)
	if n > len(p.wpend) {
		n = len(p.wpend)
	}
	for i := 0; i < n; i++ {
		data[i] = p.wpend[i]
	}
	p.wtot += n
	p.wpend = p.wpend[n:len(p.wpend)]

	// If write block is done, finish the write.
	if len(p.wpend) == 0 {
		p.wpend = nil
		p.cw <- pipeReturn(p.wtot, nil)
		p.wtot = 0
	}

	return n, nil
}

func (p *pipe) Write(data []byte) (n int, err *os.Error) {
	if p == nil || p.wclosed {
		return 0, os.EINVAL
	}
	if p.rclosed {
		return 0, os.EPIPE
	}

	// Send data to reader.
	p.cr <- data

	// Wait for reader to finish copying it.
	res := <-p.cw
	return res.n, res.err
}

func (p *pipe) CloseReader() *os.Error {
	if p == nil || p.rclosed {
		return os.EINVAL
	}

	// Stop any future writes.
	p.rclosed = true

	// Stop the current write.
	if !p.wclosed {
		p.cw <- pipeReturn(p.wtot, os.EPIPE)
	}

	return nil
}

func (p *pipe) CloseWriter() *os.Error {
	if p == nil || p.wclosed {
		return os.EINVAL
	}

	// Stop any future reads.
	p.wclosed = true

	// Stop the current read.
	if !p.rclosed {
		p.cr <- nil
	}

	return nil
}

// Read/write halves of the pipe.
// They are separate structures for two reasons:
//  1.  If one end becomes garbage without being Closed,
//      its finisher can Close so that the other end
//      does not hang indefinitely.
//  2.  Clients cannot use interface conversions on the
//      read end to find the Write method, and vice versa.

// Read half of pipe.
type pipeRead struct {
	lock sync.Mutex
	p *pipe
}

func (r *pipeRead) Read(data []byte) (n int, err *os.Error) {
	r.lock.Lock()
	defer r.lock.Unlock()

	return r.p.Read(data)
}

func (r *pipeRead) Close() *os.Error {
	r.lock.Lock()
	defer r.lock.Unlock()

	return r.p.CloseReader()
}

func (r *pipeRead) finish() {
	r.Close()
}

// Write half of pipe.
type pipeWrite struct {
	lock sync.Mutex
	p *pipe
}

func (w *pipeWrite) Write(data []byte) (n int, err *os.Error) {
	w.lock.Lock()
	defer w.lock.Unlock()

	return w.p.Write(data)
}

func (w *pipeWrite) Close() *os.Error {
	w.lock.Lock()
	defer w.lock.Unlock()

	return w.p.CloseWriter()
}

func (w *pipeWrite) finish() {
	w.Close()
}

// Create a synchronous in-memory pipe.
// Reads on one end are matched by writes on the other.
// Writes don't complete until all the data has been
// written or the read end is closed.  Reads return
// any available data or block until the next write
// or the write end is closed.
func Pipe() (io.ReadClose, io.WriteClose) {
	p := new(pipe)
	p.cr = make(chan []byte, 1)
	p.cw = make(chan pipeReturn, 1)
	r := new(pipeRead)
	r.p = p
	w := new(pipeWrite)
	w.p = p
	return r, w
}

src/lib/time/time.go (命名変更とコメント修正)

--- a/src/lib/time/time.go
+++ b/src/lib/time/time.go
@@ -9,14 +9,14 @@ import (
 	"time"
 )
 
-// Seconds since January 1, 1970 00:00:00 GMT
+// Seconds since January 1, 1970 00:00:00 UTC
 func Seconds() int64 {
 	sec, nsec, err := os.Time();
 	if err != nil {
 		return 0
 	}
 	return sec
 }
 
-// Nanoseconds since January 1, 1970 00:00:00 GMT
+// Nanoseconds since January 1, 1970 00:00:00 UTC
 func Nanoseconds() int64 {
 	sec, nsec, err := os.Time();
 	if err != nil {
@@ -61,24 +61,24 @@ func months(year int64) []int {
 }
 
 const (
-\t_SecondsPerDay = 24*60*60;
+\tsecondsPerDay = 24*60*60;
 
-\t_DaysPer400Years = 365*400+97;
-\t_DaysPer100Years = 365*100+24;
-\t_DaysPer4Years = 365*4+1;
+\tdaysPer400Years = 365*400+97;
+\tdaysPer100Years = 365*100+24;
+\tdaysPer4Years = 365*4+1;
 
-\t_Days1970To2001 = 31*365+8;
+\tdays1970To2001 = 31*365+8;
 )
 
 func SecondsToUTC(sec int64) *Time {
 	t := new(Time);
 
 	// Split into time and day.
-\tday := sec/_SecondsPerDay;
-\tsec -= day*_SecondsPerDay;
+\tday := sec/secondsPerDay;
+\tsec -= day*secondsPerDay;
 	if sec < 0 {
 		day--;
-\t\tsec += _SecondsPerDay
+\t\tsec += secondsPerDay
 	}
 
 	// Time
@@ -95,30 +95,30 @@ func SecondsToUTC(sec int64) *Time {
 	// Change day from 0 = 1970 to 0 = 2001,
 	// to make leap year calculations easier
 	// (2001 begins 4-, 100-, and 400-year cycles ending in a leap year.)
-\tday -= _Days1970To2001;
+\tday -= days1970To2001;
 
 \tyear := int64(2001);
 \tif day < 0 {
 \t\t// Go back enough 400 year cycles to make day positive.
-\t\tn := -day/_DaysPer400Years + 1;\n+\t\tn := -day/daysPer400Years + 1;
 \t\tyear -= 400*n;
-\t\tday += _DaysPer400Years*n;\n+\t\tday += daysPer400Years*n;
 \t} else {
 \t\t// Cut off 400 year cycles.
-\t\tn := day/_DaysPer400Years;\n+\t\tn := day/daysPer400Years;
 \t\tyear += 400*n;
-\t\tday -= _DaysPer400Years*n;\n+\t\tday -= daysPer400Years*n;
 \t}\n 
 \t// Cut off 100-year cycles
-\tn := day/_DaysPer100Years;\n+\tn := day/daysPer100Years;
 \tyear += 100*n;
-\tday -= _DaysPer100Years*n;\n+\tday -= daysPer100Years*n;
 \n \t// Cut off 4-year cycles
-\tn = day/_DaysPer4Years;\n+\tn = day/daysPer4Years;
 \tyear += 4*n;
-\tday -= _DaysPer4Years*n;\n+\tday -= daysPer4Years*n;
 \n \t// Cut off non-leap years.\n \tn = day/365;\
@@ -147,23 +147,23 @@ func UTC() *Time {
 \tif year < 2001 {\n \t\tn := (2001 - year)/400 + 1;\n \t\tyear += 400*n;\n-\t\tday -= _DaysPer400Years*n;\n+\t\tday -= daysPer400Years*n;\
 \t}\n \n \t// Add in days from 400-year cycles.\n \tn := (year - 2001) / 400;\n \tyear -= 400*n;\n-\tday += _DaysPer400Years*n;\n+\tday += daysPer400Years*n;\
 \n \t// Add in 100-year cycles.\n \tn = (year - 2001) / 100;\n \tyear -= 100*n;\n-\tday += _DaysPer100Years*n;\n+\tday += daysPer100Years*n;\
 \n \t// Add in 4-year cycles.\n \tn = (year - 2001) / 4;\n \tyear -= 4*n;\n-\tday += _DaysPer4Years*n;\n+\tday += daysPer4Years*n;\
 \n \t// Add in non-leap years.\n \tn = year - 2001;\
@@ -206,7 +205,7 @@ func (t *Time) Seconds() int64 {
 \tday += int64(t.Day - 1);\n \n \t// Convert days to seconds since January 1, 2001.\n-\tsec := day * _SecondsPerDay;\n+\tsec := day * secondsPerDay;\
 \n \t// Add in time elapsed today.\n \tsec += int64(t.Hour) * 3600;\
@@ -214,14 +213,14 @@ func (t *Time) Seconds() int64 {
 \tsec += int64(t.Second);\n \n \t// Convert from seconds since 2001 to seconds since 1970.\n-\tsec += _Days1970To2001 * _SecondsPerDay;\n+\tsec += days1970To2001 * secondsPerDay;\
 \n \t// Account for local time zone.\n \tsec -= int64(t.ZoneOffset);\n \treturn sec\n }\n \n-var _LongDayNames = []string(\n+var longDayNames = []string(\
 \t\"Sunday\",\n \t\"Monday\",\n \t\"Tuesday\",\
@@ -231,7 +230,7 @@ var _LongDayNames = []string(\
 \t\"Saturday\"\n )\n \n-var _ShortDayNames = []string(\n+var shortDayNames = []string(\
 \t\"Sun\",\n \t\"Mon\",\n \t\"Tue\",\
@@ -241,7 +240,7 @@ var _ShortDayNames = []string(\
 \t\"Sat\"\n )\n \n-var _ShortMonthNames = []string(\n+var shortMonthNames = []string(\
 \t\"Jan\",\n \t\"Feb\",\n \t\"Mar\",\
@@ -256,59 +255,59 @@ var _ShortMonthNames = []string(\
 \t\"Dec\"\n )\n \n-func _Copy(dst []byte, s string) {\n+func copy(dst []byte, s string) {\
 \tfor i := 0; i < len(s); i++ {\n \t\tdst[i] = s[i]\n \t}\n }\n \n-func _Decimal(dst []byte, n int) {\n+func decimal(dst []byte, n int) {\
 \tif n < 0 {\n \t\tn = 0\n \t}\n \t// TODO(rsc): use strconv.Itoa\n \tif n < 10 {\n \t\tdst[0] = \'0\';\n \t\tdst[1] = byte(n + \'0\')\n \t} else {\n \t\tdst[0] = byte(n/10 + \'0\');\n \t\tdst[1] = byte(n%10 + \'0\')\n \t}\n }\n \n-func _AddString(buf []byte, bp int, s string) int {\n+func addString(buf []byte, bp int, s string) int {\
 \tn := len(s);\n-\t_Copy(buf[bp:bp+n], s);\n+\tcopy(buf[bp:bp+n], s);\
 \treturn bp+n\n }\n \n // Just enough of strftime to implement the date formats below.\n // Not exported.\n-func _Format(t *Time, fmt string) string {\n+func format(t *Time, fmt string) string {\
 \tbuf := make([]byte, 128);\n \tbp := 0;\n \n@@ -289,39 +288,39 @@ func _Format(t *Time, fmt string) string {\
 \t\t\ti++;\n \t\t\tswitch fmt[i] {\n \t\t\tcase \'A\':\t// %A full weekday name\n-\t\t\t\tbp = _AddString(buf, bp, _LongDayNames[t.Weekday]);\n+\t\t\t\tbp = addString(buf, bp, longDayNames[t.Weekday]);\n \t\t\tcase \'a\':\t// %a abbreviated weekday name\n-\t\t\t\tbp = _AddString(buf, bp, _ShortDayNames[t.Weekday]);\n+\t\t\t\tbp = addString(buf, bp, shortDayNames[t.Weekday]);\n \t\t\tcase \'b\':\t// %b abbreviated month name\n-\t\t\t\tbp = _AddString(buf, bp, _ShortMonthNames[t.Month-1]);\n+\t\t\t\tbp = addString(buf, bp, shortMonthNames[t.Month-1]);\n \t\t\tcase \'d\':\t// %d day of month (01-31)\n-\t\t\t\t_Decimal(buf[bp:bp+2], t.Day);\n+\t\t\t\tdecimal(buf[bp:bp+2], t.Day);\
 \t\t\t\tbp += 2;\n \t\t\tcase \'e\':\t// %e day of month ( 1-31)\n \t\t\t\tif t.Day >= 10 {\n-\t\t\t\t\t_Decimal(buf[bp:bp+2], t.Day)\n+\t\t\t\t\tdecimal(buf[bp:bp+2], t.Day)\
 \t\t\t\t} else {\n \t\t\t\t\tbuf[bp] = \' \';\n \t\t\t\t\tbuf[bp+1] = byte(t.Day + \'0\')\n \t\t\t\t}\n \t\t\t\tbp += 2;\n \t\t\tcase \'H\':\t// %H hour 00-23\n-\t\t\t\t_Decimal(buf[bp:bp+2], t.Hour);\n+\t\t\t\tdecimal(buf[bp:bp+2], t.Hour);\
 \t\t\t\tbp += 2;\n \t\t\tcase \'M\':\t// %M minute 00-59\n-\t\t\t\t_Decimal(buf[bp:bp+2], t.Minute);\n+\t\t\t\tdecimal(buf[bp:bp+2], t.Minute);\
 \t\t\t\tbp += 2;\n \t\t\tcase \'S\':\t// %S second 00-59\n-\t\t\t\t_Decimal(buf[bp:bp+2], t.Second);\n+\t\t\t\tdecimal(buf[bp:bp+2], t.Second);\
 \t\t\t\tbp += 2;\n \t\t\tcase \'Y\':\t// %Y year 2008\n-\t\t\t\t_Decimal(buf[bp:bp+4], int(t.Year));\n+\t\t\t\tdecimal(buf[bp:bp+4], int(t.Year));
 \t\t\t\tbp += 4;\n \t\t\tcase \'y\':\t// %y year 08\n-\t\t\t\t_Decimal(buf[bp:bp+2], int(t.Year%100));\n+\t\t\t\tdecimal(buf[bp:bp+2], int(t.Year%100));
 \t\t\t\tbp += 2;\n \t\t\tcase \'Z\':\n-\t\t\t\tbp = _AddString(buf, bp, t.Zone);\n+\t\t\t\tbp = addString(buf, bp, t.Zone);\
 \t\t\tdefault:\n \t\t\t\tbuf[bp] = \'%\';\n \t\t\t\tbuf[bp+1] = fmt[i];\
@@ -337,21 +336,21 @@ func _Format(t *Time, fmt string) string {
 
 // ANSI C asctime: Sun Nov  6 08:49:37 1994
 func (t *Time) Asctime() string {
-\treturn _Format(t, \"%a %b %e %H:%M:%S %Y\")\n+\treturn format(t, \"%a %b %e %H:%M:%S %Y\")
 }
 
-// RFC 850: Sunday, 06-Nov-94 08:49:37 GMT
+// RFC 850: Sunday, 06-Nov-94 08:49:37 UTC
 func (t *Time) RFC850() string {
-\treturn _Format(t, \"%A, %d-%b-%y %H:%M:%S %Z\")\n+\treturn format(t, \"%A, %d-%b-%y %H:%M:%S %Z\")
 }
 
-// RFC 1123: Sun, 06 Nov 1994 08:49:37 GMT
+// RFC 1123: Sun, 06 Nov 1994 08:49:37 UTC
 func (t *Time) RFC1123() string {
-\treturn _Format(t, \"%a, %d %b %Y %H:%M:%S %Z\")\n+\treturn format(t, \"%a, %d %b %Y %H:%M:%S %Z\")
 }
 
-// date(1) - Sun Nov  6 08:49:37 GMT 1994
+// date(1) - Sun Nov  6 08:49:37 UTC 1994
 func (t *Time) String() string {
-\treturn _Format(t, \"%a %b %e %H:%M:%S %Z %Y\")\n+\treturn format(t, \"%a %b %e %H:%M:%S %Z %Y\")
 }

コアとなるコードの解説

io.goの変更

  • 新しいインターフェースの導入:
    • Closeインターフェースが独立して定義されました。これは、リソースをクローズする機能だけを持つ型のためのものです。
    • ReadCloseWriteCloseインターフェースが追加されました。これらは、それぞれ読み込みとクローズ、書き込みとクローズの機能を組み合わせたものです。io.Pipeが返す値の型として使用され、パイプの各端が持つべき機能を明確に示します。
    • 既存のReadWriteCloseインターフェースの定義が、新しいCloseインターフェースの導入に合わせて修正されました。
  • アンダースコア命名規則の整理:
    • _FullRead構造体とMake_FullReader関数から先頭のアンダースコアが削除され、それぞれfullReadMakeFullReaderになりました。これは、Goの命名規則に従い、パッケージ内部でのみ使用される識別子には小文字で始まる名前を使用するという慣習に合わせたものです。機能的な変更はありません。

pipe.goの新規追加

このファイルはio.Pipe機能の全てを実装しています。

  • pipe構造体: パイプの共有状態を管理します。
    • rclosed, wclosed: 読み書き両端のクローズ状態を追跡します。
    • wpend, wtot: 書き込み中のデータと、そのうち読み込み済みの量を管理します。これにより、部分的な読み込みと書き込みが可能です。
    • cr chan []byte: 書き込み側から読み込み側へデータを送るためのチャネル。バッファなし(またはバッファサイズ1)チャネルとして機能し、書き込みは読み込みがデータを受け取るまでブロックされます。
    • cw chan pipeReturn: 読み込み側から書き込み側へ、読み込み完了の通知と結果(読み込んだバイト数とエラー)を返すためのチャネル。書き込みは読み込みが完了するまでブロックされます。
  • pipe.Read()メソッド:
    • 読み込み側がクローズされている場合や、無効なパイプの場合はエラーを返します。
    • wpendが空の場合、crチャネルから新しいデータブロックを待ち受けます。これにより、書き込み側がデータを送信するまで読み込み側はブロックされます。
    • 受け取ったデータから、要求されたバイト数をdataバッファにコピーします。
    • データブロックをすべて読み込んだ場合、cwチャネルに読み込み完了の通知を送信し、書き込み側をブロック解除します。
  • pipe.Write()メソッド:
    • 書き込み側がクローズされている場合や、読み込み側がクローズされている(パイプが壊れている)場合はエラーを返します。
    • crチャネルにデータを送信します。この操作は、読み込み側がデータを受け取るまでブロックされます。
    • cwチャネルから読み込み結果を待ち受けます。この操作は、読み込み側がデータを処理し、結果を返すまでブロックされます。
  • pipe.CloseReader()pipe.CloseWriter()メソッド:
    • それぞれの端をクローズし、対応するチャネルにシグナルを送ることで、もう一方の端が適切に終了できるようにします。例えば、CloseReader()は、書き込み側がos.EPIPEエラーを受け取るようにします。
  • pipeReadpipeWrite構造体:
    • それぞれio.Reader/io.Closerio.Writer/io.Closerインターフェースを実装します。
    • sync.Mutexを保持し、それぞれの操作(ReadWrite)が排他的に実行されるように保護します。これにより、複数のgoroutineが同時に同じパイプの端にアクセスしても安全性が保たれます。
    • finish()メソッドは、Goの初期のファイナライザの概念で、オブジェクトがガベージコレクションされる際に自動的にClose()を呼び出すためのものです。
  • Pipe()関数:
    • io.Pipeのコンストラクタ関数です。
    • 新しいpipe構造体を初期化し、その読み込み側と書き込み側を表すpipeReadpipeWriteのインスタンスを生成して返します。

time.goの変更

  • コメントの修正:
    • 時間に関するコメントで「GMT」(グリニッジ標準時)が「UTC」(協定世界時)に修正されました。UTCはより現代的で正確な時間標準です。
  • アンダースコア命名規則の整理:
    • _SecondsPerDayなどの定数や、_Copy_Decimal_AddString_Formatなどの内部関数から先頭のアンダースコアが削除されました。これらはio.goの変更と同様に、Goの命名規則に準拠するためのスタイル変更です。

これらの変更は、Go言語の標準ライブラリが、並行処理のサポート、堅牢なI/O操作、そして一貫したコーディングスタイルを追求して進化していく過程を示しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (上記「関連リンク」に記載)
  • コミットの差分情報 (git diff)
  • Go言語の設計原則と歴史に関する一般的な知識