[インデックス 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
パッケージの既存のインターフェース定義が整理され、Close
、ReadClose
、WriteClose
といった新しいインターフェースが追加されています。また、内部的な構造体や関数の命名が、Goの慣習に合わせて先頭のアンダースコアが削除されています(例:_FullRead
からfullRead
、Make_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の標準ライブラリの基盤を構築する上で重要な役割を果たしています。
-
io.Pipe
の導入: Go言語はgoroutineとチャネルによる並行処理を強力にサポートしています。プログラム内でデータをストリームとして扱う際、あるgoroutineがデータを生成し(io.Writer
として)、別のgoroutineがそのデータを消費する(io.Reader
として)というパターンは非常に一般的です。しかし、これら二つの異なるインターフェースを持つコンポーネントを直接接続するメカニズムが不足していました。io.Pipe
は、このようなシナリオにおいて、io.Writer
とio.Reader
のインターフェースを介して、goroutine間で同期的にデータをやり取りするためのシンプルなインメモリパイプを提供することを目的として導入されました。これにより、例えば、データを圧縮するgoroutineと、その圧縮されたデータをネットワークに送信するgoroutineを容易に連結できるようになります。Unixのパイプ(|
)に似た概念をGoのプロセス内並行処理に持ち込むものです。 -
アンダースコア命名規則の整理: Go言語の設計哲学の一つに「シンプルさと明瞭さ」があります。初期のGoコードベースでは、内部的な(エクスポートされない)識別子にアンダースコアをプレフィックスとして付ける慣習が見られました(例:
_FullRead
)。しかし、Goの公式な命名規則では、エクスポートされない識別子は小文字で始まることになっており、アンダースコアをプレフィックスとして使用することは推奨されていません。このコミットは、このような初期の慣習をGoの標準的な命名規則に合わせるためのコードベース全体のクリーンアップの一環として行われました。これにより、コードの一貫性が向上し、可読性が高まります。 -
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.Writer
とio.Reader
の間でデータをやり取りするための主要な手段として利用されています。
3. sync.Mutex
(排他制御)
sync.Mutex
は、Goの標準ライブラリsync
パッケージで提供される相互排他ロック(ミューテックス)です。複数のgoroutineが共有リソースに同時にアクセスするのを防ぎ、データ競合を回避するために使用されます。
Lock()
: ロックを取得します。既にロックが取得されている場合は、ロックが解放されるまでブロックします。Unlock()
: ロックを解放します。
io.Pipe
の実装では、pipeRead
とpipeWrite
構造体がそれぞれsync.Mutex
を保持し、Read
およびWrite
操作が同時に実行されないように保護しています。これにより、パイプの内部状態の一貫性が保たれます。
4. パイプの概念
「パイプ」は、一方のプロセス(またはGoの場合はgoroutine)の出力が、もう一方のプロセス(またはgoroutine)の入力に直接接続される通信メカニズムです。Unix系OSでは、コマンドラインで|
(パイプ)を使ってプログラムの出力を別のプログラムの入力に渡すことができます(例: ls -l | grep .txt
)。
io.Pipe
は、この概念をGoプログラムのメモリ内で実現します。Pipe()
関数は、io.Reader
とio.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.Reader
とio.Closer
インターフェースを実装します。type pipeRead struct { lock sync.Mutex p *pipe }
lock
: 読み込み操作の排他制御のためのミューテックス。p
: 共有のpipe
構造体へのポインタ。
-
pipeWrite
構造体: パイプの書き込み側を表す構造体。io.Writer
とio.Closer
インターフェースを実装します。type pipeWrite struct { lock sync.Mutex p *pipe }
lock
: 書き込み操作の排他制御のためのミューテックス。p
: 共有のpipe
構造体へのポインタ。
Pipe()
関数の動作:
Pipe()
関数は、新しいpipe
構造体を初期化し、そのcr
とcw
チャネルを作成します。そして、そのpipe
構造体へのポインタを持つpipeRead
とpipeWrite
のインスタンスを生成し、それぞれio.ReadClose
とio.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)
:- 書き込み側がクローズされているか、読み込み側がクローズされているかを確認し、エラーを返します。
p.cr <- data
によって、書き込むデータをcr
チャネルに送信します。この操作は、読み込み側がcr
チャネルからデータを受け取るまでブロックされます。res := <-p.cw
によって、読み込み側からの処理結果をcw
チャネルから受け取ります。この操作は、読み込み側が処理を完了し、結果をcw
チャネルに送信するまでブロックされます。- 受け取った結果(書き込んだバイト数とエラー)を返します。
-
pipe.Read(data []byte)
:- 読み込み側がクローズされているかを確認し、エラーを返します。
p.wpend == nil
の場合(つまり、まだ読み込むべきデータがない場合)、p.wpend = <-p.cr
によってcr
チャネルから新しいデータブロックを受け取ります。この操作は、書き込み側がcr
チャネルにデータを送信するまでブロックされます。- 受け取ったデータブロック(
p.wpend
)から、要求されたバイト数(len(data)
またはlen(p.wpend)
の小さい方)をdata
バッファにコピーします。 - コピーしたバイト数だけ
p.wpend
をスライスし、p.wtot
を更新します。 len(p.wpend) == 0
の場合(つまり、現在のデータブロックをすべて読み込んだ場合)、p.cw <- pipeReturn(p.wtot, nil)
によって、読み込んだバイト数とエラー(なし)をcw
チャネルに送信します。これにより、書き込み側がブロック解除されます。- 読み込んだバイト数を返します。
このチャネルを介した同期的なやり取りにより、書き込み側は読み込み側がデータを消費するまで待機し、読み込み側は書き込み側がデータを生成するまで待機するという、効率的かつ安全なデータフローが実現されます。
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)として扱われます。
pipeRead
とpipeWrite
のClose()
メソッドは、それぞれ対応するpipe
のCloseReader()
とCloseWriter()
を呼び出します。また、finish()
メソッドも定義されており、これはガベージコレクション時に自動的にClose()
を呼び出すためのものです(Goの初期のファイナライザの概念)。
アンダースコア命名規則の整理
このコミットでは、Goの命名規則に準拠するため、内部的な識別子から先頭のアンダースコアが削除されています。
src/lib/io/io.go
の例:
type _FullRead struct
がtype fullRead struct
に変更。func Make_FullReader
がfunc MakeFullReader
に変更。
src/lib/time/time.go
の例:
const _SecondsPerDay
がconst secondsPerDay
に変更。var _LongDayNames
がvar longDayNames
に変更。func _Copy
がfunc copy
に変更。func _Decimal
がfunc decimal
に変更。func _AddString
がfunc addString
に変更。func _Format
がfunc 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
インターフェースが独立して定義されました。これは、リソースをクローズする機能だけを持つ型のためのものです。ReadClose
とWriteClose
インターフェースが追加されました。これらは、それぞれ読み込みとクローズ、書き込みとクローズの機能を組み合わせたものです。io.Pipe
が返す値の型として使用され、パイプの各端が持つべき機能を明確に示します。- 既存の
ReadWriteClose
インターフェースの定義が、新しいClose
インターフェースの導入に合わせて修正されました。
- アンダースコア命名規則の整理:
_FullRead
構造体とMake_FullReader
関数から先頭のアンダースコアが削除され、それぞれfullRead
とMakeFullReader
になりました。これは、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
エラーを受け取るようにします。
- それぞれの端をクローズし、対応するチャネルにシグナルを送ることで、もう一方の端が適切に終了できるようにします。例えば、
pipeRead
とpipeWrite
構造体:- それぞれ
io.Reader
/io.Closer
とio.Writer
/io.Closer
インターフェースを実装します。 sync.Mutex
を保持し、それぞれの操作(Read
やWrite
)が排他的に実行されるように保護します。これにより、複数のgoroutineが同時に同じパイプの端にアクセスしても安全性が保たれます。finish()
メソッドは、Goの初期のファイナライザの概念で、オブジェクトがガベージコレクションされる際に自動的にClose()
を呼び出すためのものです。
- それぞれ
Pipe()
関数:io.Pipe
のコンストラクタ関数です。- 新しい
pipe
構造体を初期化し、その読み込み側と書き込み側を表すpipeRead
とpipeWrite
のインスタンスを生成して返します。
time.go
の変更
- コメントの修正:
- 時間に関するコメントで「GMT」(グリニッジ標準時)が「UTC」(協定世界時)に修正されました。UTCはより現代的で正確な時間標準です。
- アンダースコア命名規則の整理:
_SecondsPerDay
などの定数や、_Copy
、_Decimal
、_AddString
、_Format
などの内部関数から先頭のアンダースコアが削除されました。これらはio.go
の変更と同様に、Goの命名規則に準拠するためのスタイル変更です。
これらの変更は、Go言語の標準ライブラリが、並行処理のサポート、堅牢なI/O操作、そして一貫したコーディングスタイルを追求して進化していく過程を示しています。
関連リンク
- Go言語の
io
パッケージに関する公式ドキュメント: https://pkg.go.dev/io - Go言語の
sync
パッケージに関する公式ドキュメント: https://pkg.go.dev/sync - Go言語の
time
パッケージに関する公式ドキュメント: https://pkg.go.dev/time - Go言語のEffective Go - Naming: https://go.dev/doc/effective_go#names
参考にした情報源リンク
- Go言語の公式ドキュメント (上記「関連リンク」に記載)
- コミットの差分情報 (
git diff
) - Go言語の設計原則と歴史に関する一般的な知識