[インデックス 1375] ファイルの概要
このコミットは、Go言語の初期開発段階における重要な変更を記録しています。具体的には、map型とchan型の宣言からポインタを示す*記号を削除し、new(T)の挙動をnew(*T)に変更することで、言語の型システムとメモリ管理モデルを現代のGo言語のセマンティクスに近づけることを目的としています。また、*[]から[]へのスライス型の変換に伴う残存バグの修正も含まれています。
コミット
commit 08ca30bbfad04d3ca1bf7ae75c291b91ecb00aef
Author: Russ Cox <rsc@golang.org>
Date: Fri Dec 19 03:05:37 2008 -0800
change *map to map; *chan to chan; new(T) to new(*T)
fix bugs left over from *[] to [] conversion.
TBR=r
OCL=21576
CL=21581
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/08ca30bbfad04d3ca1bf7ae75c291b91ecb00aef
元コミット内容
change *map to map; *chan to chan; new(T) to new(*T)
fix bugs left over from *[] to [] conversion.
変更の背景
Go言語は、設計段階からシンプルさと効率性を追求していました。このコミットが行われた2008年12月は、Go言語がまだ一般に公開される前の非常に初期の段階です。当時のGo言語の型システムは、現在とは異なるセマンティクスを持っていました。特に、mapやchanといった組み込みの参照型が、宣言時に明示的なポインタ記号*を必要としていたことは、冗長であり、言語の哲学である「シンプルさ」に反していました。
また、new組み込み関数の挙動も、現在のGoとは異なっていたようです。現在のGoではnew(T)は型Tのゼロ値が格納された新しいメモリ領域を割り当て、その領域へのポインタ*Tを返します。しかし、このコミットの変更内容から、以前はnew(T)が直接T型の値を返していたか、あるいはそのポインタの扱いが不明瞭であった可能性が示唆されます。この変更は、newのセマンティクスを明確にし、一貫性のあるメモリ割り当てモデルを確立するためのものでした。
さらに、スライス([])の扱いについても、以前は*[]のようなポインタとしての側面が強調されていたようです。このコミットは、スライスをより値型に近い形で扱えるようにする変更(*[]から[]への変換)の残存バグを修正することで、スライスのセマンティクスを安定させることを目的としています。これらの変更は、Go言語がより洗練され、使いやすい言語へと進化する過程で不可欠なステップでした。
前提知識の解説
このコミットを理解するためには、Go言語の基本的な型システムとメモリ管理に関する初期の設計思想、および現在のGo言語のセマンティクスを比較して理解することが重要です。
- ポインタ (Pointers): Go言語におけるポインタは、変数のメモリアドレスを指し示します。
*Tは型Tへのポインタを表します。 - マップ (Maps): Go言語のマップは、キーと値のペアを格納するハッシュテーブルです。現在のGoでは、マップは参照型であり、
make関数で初期化されます。マップ変数は、内部的にマップのデータ構造へのポインタを保持していますが、宣言時にはmap[KeyType]ValueTypeのように*は不要です。 - チャネル (Channels): Go言語のチャネルは、ゴルーチン間で値を送受信するための通信メカニズムです。現在のGoでは、チャネルも参照型であり、
make関数で初期化されます。チャネル変数は、内部的にチャネルのデータ構造へのポインタを保持しますが、宣言時にはchan ElementTypeのように*は不要です。 - スライス (Slices): Go言語のスライスは、配列の一部を参照する動的なデータ構造です。スライスは、内部的に基になる配列へのポインタ、長さ、容量の3つの要素から構成される「スライスヘッダ」と呼ばれる構造体です。スライス自体は値型として扱われますが、その内部のポインタを通じて基になる配列のデータを共有・変更できます。
new組み込み関数: 現在のGo言語において、new(T)は型Tのゼロ値が格納された新しいメモリ領域を割り当て、その領域へのポインタ*Tを返します。これは、C++のnew TやJavaのnew T()とは異なり、オブジェクトのコンストラクタを呼び出すわけではありません。
このコミットは、これらの要素が現在のGo言語のセマンティクスに収束していく過程を示しています。
技術的詳細
このコミットの技術的詳細は、Go言語のコンパイラとランタイムが、map、chan、new、およびスライスの内部表現と外部インターフェースをどのように変更したかに集約されます。
-
*mapからmapへの変更:- 以前のGoでは、マップ型を宣言する際に
*map[KeyType]ValueTypeのように明示的なポインタ記号が必要だったと考えられます。これは、マップが内部的にポインタとして実装されていることを言語レベルで強制していたことを意味します。 - この変更により、
map[KeyType]ValueTypeという宣言が可能になり、マップがより「組み込み型」として自然に扱えるようになりました。これは、言語の構文を簡素化し、開発者がマップの内部実装(ポインタであること)を意識することなく、その抽象的な機能に集中できるようにするための変更です。 - コンパイラは、
map型の宣言を検出した際に、自動的に適切なポインタのセマンティクスを適用するように変更されたと推測されます。
- 以前のGoでは、マップ型を宣言する際に
-
*chanからchanへの変更:mapと同様に、チャネル型も以前は*chan ElementTypeのように宣言されていた可能性があります。- この変更により、
chan ElementTypeという宣言が可能になり、チャネルもマップと同様に、より自然な組み込み型として扱えるようになりました。 - これにより、チャネルの利用がより直感的になり、並行処理の記述が簡潔になったと考えられます。
-
new(T)からnew(*T)への変更:- これは最も重要な変更点の一つです。現在のGoでは、
new(T)は常に*T型の値を返します。このコミットのメッセージ「new(T)tonew(*T)」は、以前のnew(T)がT型の値を直接返していたか、あるいはその挙動が曖昧であったことを示唆しています。 - この変更は、
newのセマンティクスを明確にし、Go言語におけるメモリ割り当てのモデルを統一するためのものです。newは常にポインタを返すという一貫したルールを確立することで、開発者は値型と参照型の区別をより明確に意識できるようになりました。 - これにより、Go言語のメモリ管理モデルがより予測可能になり、ガベージコレクションの効率化にも寄与した可能性があります。
- これは最も重要な変更点の一つです。現在のGoでは、
-
*[]から[]へのスライス変換バグ修正:- スライスは、Go言語の強力な機能の一つですが、その内部構造(ポインタ、長さ、容量)と値型としての振る舞いのバランスは、設計上難しい課題でした。
- 「
*[]to[]conversion」という記述は、スライスが以前は明示的なポインタ型として扱われていた時期があり、それを現在の値型に近いセマンティクス(ただし内部的にはポインタを持つ)に変更する過程で、いくつかのバグが発生していたことを示しています。 - このコミットは、その変換プロセスで生じた残存バグを修正し、スライスの挙動を安定させることを目的としています。これにより、スライスのコピー、引数渡し、および基になる配列との相互作用が、より予測可能で堅牢になったと考えられます。
これらの変更は、Go言語がその初期段階で直面した設計上の課題を解決し、現在のシンプルで効率的な言語へと進化するための基盤を築いたと言えます。
コアとなるコードの変更箇所
このコミットは広範囲にわたる変更を含んでいますが、特に注目すべきは以下のパターンです。
-
new(Type)がnew(*Type)に変更されている箇所:src/lib/bignum.go:new(Natural, n + 1)->new(*Natural, n + 1)など、newの呼び出しが多数変更されています。src/lib/bufio.go:new(BufRead)->new(*BufRead)src/lib/container/array/array.go:new(Array)->new(*Array)src/lib/flag.go:new(Flags)->new(*Flags)src/lib/hash/md5.go:new(Digest)->new(*Digest)src/lib/http/conn.go:new(Conn)->new(*Conn)src/lib/json/generic.go:new(JsonBuilder)->new(*JsonBuilder)src/lib/net/dnsmsg.go:new(DNS_Msg)->new(*DNS_Msg)src/lib/net/fd.go:new(PollServer)->new(*PollServer)src/lib/regexp/regexp.go:new(Char)->new(*Char)src/lib/strconv/atof.go:new(Decimal)->new(*Decimal)src/lib/sync/mutex_test.go:new(int32)->new(*int32)src/lib/time/time.go:new(Time)->new(*Time)src/lib/tabwriter/tabwriter.go:new(Writer)->new(*Writer)src/lib/testing.go:new(T)->new(*T)
-
*mapがmapに変更されている箇所:src/lib/flag.go:actual *map[string] *Flag;->actual map[string] *Flag;src/lib/json/generic.go:type Map struct { m *map[string]Json; Null }->type Map struct { m map[string]Json; Null }src/lib/net/port.go:var services *map[string] *map[string] int->var services map[string] map[string] int
-
*chanがchanに変更されている箇所:src/lib/net/fd.go:cr *chan *FD;->cr chan *FD;src/lib/regexp/regexp.go:ch *chan<- *RE;->ch chan<- *RE;src/lib/once.go:doit *chan bool;->doit chan bool;src/lib/testing.go:ch *chan *T;->ch chan *T;src/lib/time/tick.go:c *chan int64->c chan int64
-
NIL []byteの削除とnilへの置き換え:src/lib/bufio.go:return NIL, b.err->return nil, b.errsrc/lib/net/dnsclient.go:var NIL []stringの削除とnilへの置き換えsrc/lib/net/ip.go:var NIL []byteの削除とnilへの置き換えsrc/lib/net/net.go:var NIL []byteの削除とnilへの置き換えsrc/lib/net/fd_darwin.go:var NIL []syscall.Keventの削除とnilへの置き換えsrc/lib/time/zoneinfo.go:var NIL []byteの削除とnilへの置き換え
これらの変更は、Go言語のコードベース全体にわたる大規模なリファクタリングであり、言語のセマンティクスを統一し、より現代的なGo言語の姿に近づけるための重要なステップであったことが伺えます。
コアとなるコードの解説
このコミットにおけるコアとなるコードの変更は、Go言語の型システムとメモリ割り当ての根本的なセマンティクスを変更するものです。
new(T) から new(*T) への変更の例 (src/lib/bignum.go より):
--- a/src/lib/bignum.go
+++ b/src/lib/bignum.go
@@ -164,9 +164,9 @@ func (x *Natural) Add(y *Natural) *Natural {
if n < m {
return y.Add(x);
}
-
+
c := Digit(0);
- z := new(Natural, n + 1);
+ z := new(*Natural, n + 1);
i := 0;
for i < m {
t := c + x[i] + y[i];
この変更は、new組み込み関数の挙動が、型Naturalのインスタンスを直接返すのではなく、Natural型へのポインタを返すように変更されたことを示しています。これにより、newは常にポインタを返すという現在のGoのセマンティクスが確立されました。
*map から map への変更の例 (src/lib/flag.go より):
--- a/src/lib/flag.go
+++ b/src/lib/flag.go
@@ -289,8 +289,8 @@ export type Flag struct {
}
type Flags struct {
- actual *map[string] *Flag;
- formal *map[string] *Flag;
+ actual map[string] *Flag;
+ formal map[string] *Flag;
first_arg int;
flag_list *Flag; // BUG: remove when we can iterate over maps
}
ここでは、Flags構造体のフィールドactualとformalの型が*map[string] *Flagからmap[string] *Flagに変更されています。これは、マップ型自体がポインタとして宣言される必要がなくなり、より自然な構文でマップを扱えるようになったことを示しています。Go言語のマップは内部的には参照型ですが、この変更により、その宣言構文が簡素化されました。
*chan から chan への変更の例 (src/lib/net/fd.go より):
--- a/src/lib/net/fd.go
+++ b/src/lib/net/fd.go
@@ -19,8 +19,8 @@ export type FD struct {
// immutable until Close
fd int64;
osfd *os.FD;
- cr *chan *FD;
- cw *chan *FD;
+ cr chan *FD;
+ cw chan *FD;
// owned by fd wait server
ncr, ncw int;
FD構造体のフィールドcrとcwの型が*chan *FDからchan *FDに変更されています。これは、チャネル型もマップと同様に、宣言時に明示的なポインタ記号が不要になったことを示しています。これにより、チャネルの宣言がより簡潔になり、Goの並行処理モデルの記述がより自然になりました。
NIL []byte の削除と nil への置き換えの例 (src/lib/bufio.go より):
--- a/src/lib/bufio.go
+++ b/src/lib/bufio.go
@@ -191,11 +191,9 @@ func (b *BufRead) Buffered() int {
// For internal (or advanced) use only.
// Use ReadLineString or ReadLineBytes instead.
-var NIL []byte // TODO(rsc): should be able to use nil
-
func (b *BufRead) ReadLineSlice(delim byte) (line []byte, err *os.Error) {
if b.err != nil {
- return NIL, b.err
+ return nil, b.err
}
この変更は、以前はNIL []byteというグローバル変数が空のスライスを表すために使われていたが、このコミットでそれが削除され、Go言語の組み込みのnil値が空のスライスやポインタを表すために一貫して使用されるようになったことを示しています。これは、言語のセマンティクスを簡素化し、冗長な定義を排除するためのクリーンアップです。
これらの変更は、Go言語がその初期段階で、より一貫性があり、直感的で、効率的な言語となるために、型システムとメモリ管理のセマンティクスをどのように進化させてきたかを示す重要な証拠です。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/doc/
- Go言語の歴史 (Go Blog): https://go.dev/blog/history
- Go言語の仕様 (The Go Programming Language Specification): https://go.dev/ref/spec
参考にした情報源リンク
- Go言語のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
- Go言語の
new組み込み関数に関するドキュメント: https://go.dev/doc/effective_go#allocation_new - Go言語のマップに関するドキュメント: https://go.dev/doc/effective_go#maps
- Go言語のチャネルに関するドキュメント: https://go.dev/doc/effective_go#channels
- Go言語のスライスに関するドキュメント: https://go.dev/doc/effective_go#slices
- Go言語の初期の設計に関する議論 (Go Mailing List archivesなど) - 特定のリンクは難しいが、Goの歴史に関するブログ記事や初期の設計ドキュメントが参考になる。