[インデックス 1417] ファイルの概要
このコミットは、Go言語の初期開発段階における重要な変更点を示しています。特に、メモリ割り当てとデータ構造の初期化に使用される組み込み関数 new
と make
のセマンティクスを明確に分離し、言語の整合性と可読性を向上させることを目的としています。
コミット
commit 5564504507c9b307840a5d13d9700d46e2a8524d
Author: Russ Cox <rsc@golang.org>
Date: Tue Jan 6 15:19:02 2009 -0800
new new & make
R=r
OCL=22166
CL=22166
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5564504507c9b307840a5d13d9700d46e2a8524d
元コミット内容
このコミットの元々の内容は、Go言語の組み込み関数 new
と make
の使用方法を統一し、new([]Type, size)
のようなスライス、マップ、チャネルの初期化における new
の使用を make([]Type, size)
に置き換えるものです。これにより、new
は型のゼロ値を持つ新しいインスタンスへのポインタを割り当てる用途に特化され、make
はスライス、マップ、チャネルといった参照型のデータ構造を初期化する用途に特化されました。
変更の背景
Go言語の初期設計において、new
はあらゆる型のメモリ割り当てに使用されていました。しかし、スライス、マップ、チャネルは単なるメモリ領域の確保だけでなく、内部的なデータ構造(例えば、スライスの場合は基盤となる配列、マップの場合はハッシュテーブル、チャネルの場合はバッファ)の初期化が必要です。new
をこれらの型に使用すると、その動作が他のプリミティブ型や構造体の割り当てとは異なり、混乱を招く可能性がありました。
このコミットは、new
と make
の役割を明確に分離することで、以下の利点をもたらしました。
- 明確性:
new(T)
は常に*T
型のポインタを返し、その指す値はT
のゼロ値で初期化されます。一方、make(T, args)
はT
型(スライス、マップ、チャネルのみ)の初期化されたインスタンスを返します。これにより、コードの意図がより明確になります。 - 型安全性とコンパイル時チェック:
make
はスライス、マップ、チャネルに特化しているため、コンパイラはこれらの型に適切な引数が渡されているかをより厳密にチェックできます。 - 一貫性: 言語全体でメモリ割り当てとデータ構造初期化のパターンが一貫したものになります。
前提知識の解説
このコミットの理解には、Go言語における以下の基本的な概念の理解が不可欠です。
- ポインタ (Pointers): Go言語では、変数のメモリアドレスを指すポインタを使用できます。
*T
は型T
の値へのポインタを表します。 - ゼロ値 (Zero Values): Go言語では、変数が宣言されると自動的にその型のゼロ値で初期化されます。数値型は0、ブール型はfalse、文字列型は空文字列、ポインタ、スライス、マップ、チャネルは
nil
です。 new
組み込み関数:new(Type)
の形式で使用されます。Type
型の新しい項目にメモリを割り当て、そのゼロ値で初期化し、その項目へのポインタ (*Type
) を返します。- 例:
p := new(int)
はint
型のゼロ値 (0) を持つメモリを割り当て、そのアドレスをp
に格納します。p
の型は*int
です。
make
組み込み関数:make
はスライス、マップ、チャネルの3つの組み込み参照型にのみ使用されます。- これらの型は、使用する前に内部データ構造を初期化する必要があります。
make
はこの初期化を行い、初期化された(ただしゼロ値で埋められた)インスタンスを返します。 make([]Type, length, capacity)
: スライスを作成します。length
はスライスの初期要素数、capacity
は基盤となる配列の容量です。make(map[KeyType]ValueType)
: マップを作成します。make(chan Type, capacity)
: チャネルを作成します。capacity
はチャネルのバッファサイズです。
- スライス (Slices): Goのスライスは、配列の一部を参照する動的なビューです。スライス自体は、基盤となる配列へのポインタ、長さ、容量の3つの要素から構成される構造体です。
- マップ (Maps): キーと値のペアを格納するハッシュテーブルです。
- チャネル (Channels): ゴルーチン間で値を送受信するための通信メカニズムです。
技術的詳細
このコミットの核心は、Go言語のメモリ管理とデータ構造の初期化における設計哲学の進化にあります。
以前のGo言語では、new
は汎用的なメモリ割り当て関数として機能していました。例えば、スライスを初期化する際に new([]byte, size)
のように記述することができました。しかし、これは new
の一般的なセマンティクス(ゼロ値を持つ単一のインスタンスへのポインタを返す)と矛盾していました。スライスは単一の値を指すポインタではなく、基盤となる配列、長さ、容量という3つの要素を持つ構造体であり、その基盤となる配列自体がメモリ上に確保され、初期化される必要があります。
make
は、このような複雑な内部構造を持つ参照型(スライス、マップ、チャネル)のために特別に導入されました。make
は、単にメモリを割り当てるだけでなく、これらのデータ構造が正しく機能するために必要な内部状態を適切に初期化します。
この変更により、コードの意図がより明確になります。
new(T)
を見れば、開発者はT
型の単一のゼロ値インスタンスへのポインタが作成されることを即座に理解できます。make(T, ...)
を見れば、開発者はT
がスライス、マップ、チャネルのいずれかであり、そのデータ構造が使用可能な状態に初期化されることを理解できます。
これは、言語の設計における「明示的であること」と「混乱を避けること」という原則を反映しています。
コアとなるコードの変更箇所
このコミットでは、Go言語の標準ライブラリ全体にわたって、スライス、マップ、チャネルの初期化における new
の使用が make
に置き換えられています。以下にいくつかの代表的な変更例を挙げます。
1. スライスの初期化 (new([]Type, size)
から make([]Type, size)
へ)
src/lib/bignum.go
の例:
--- a/src/lib/bignum.go
+++ b/src/lib/bignum.go
@@ -166,7 +166,7 @@ func (x Natural) Add(y Natural) Natural {
}
c := Digit(0);
- z := new(Natural, n + 1);
+ z := make(Natural, n + 1);
i := 0;
for i < m {
t := c + x[i] + y[i];
src/lib/bufio.go
の例:
--- a/src/lib/bufio.go
+++ b/src/lib/bufio.go
@@ -262,7 +262,7 @@ func (b *BufRead) ReadLineBytes(delim byte) (line []byte, err *os.Error) {
}
// Read bytes out of buffer.
- buf := new([]byte, b.Buffered());
+ buf := make([]byte, b.Buffered());
var n int;
n, e = b.Read(buf);
if e != nil {
2. マップの初期化 (new(map[Key]Value)
から make(map[Key]Value)
へ)
src/lib/flag.go
の例:
--- a/src/lib/flag.go
+++ b/src/lib/flag.go
@@ -318,10 +318,10 @@ func (f *Flag) SVal() string {
}
func New() *Flags {
- f := new(*Flags);
+ f := new(Flags);
f.first_arg = 1; // 0 is the program name, 1 is first arg
- f.actual = new(map[string] *Flag);
- f.formal = new(map[string] *Flag);
+ f.actual = make(map[string] *Flag);
+ f.formal = make(map[string] *Flag);
return f;
}
3. チャネルの初期化 (new(chan Type)
から make(chan Type)
へ)
src/lib/once.go
の例:
--- a/src/lib/once.go
+++ b/src/lib/once.go
@@ -22,8 +22,8 @@ type Request struct {
}
// TODO: Would like to use chan Request but 6g rejects it.
-var service = new(chan *Request)
-var jobmap = new(map[*()]*Job)
+var service = make(chan *Request)
+var jobmap = make(map[*()]*Job)
4. 構造体インスタンスの割り当て (new(*Type)
から new(Type)
へ)
これは new
のセマンティクスが明確化された結果、ポインタ型を引数に取る必要がなくなった例です。new(Type)
は *Type
を返すため、new(*Type)
は **Type
を返してしまい、冗長で混乱を招く可能性がありました。
src/lib/bufio.go
の例:
--- a/src/lib/bufio.go
+++ b/src/lib/bufio.go
@@ -50,8 +50,8 @@ export func NewBufReadSize(rd io.Read, size int) (b *BufRead, err *os.Error) {
if size <= 0 {
return nil, BadBufSize
}
- b = new(*BufRead);
- b.buf = new([]byte, size);
+ b = new(BufRead);
+ b.buf = make([]byte, size);
b.rd = rd;
return b, nil
}
コアとなるコードの解説
上記の変更は、Go言語のメモリ割り当てとデータ構造の初期化に関する設計原則を強化するものです。
-
new(Type)
の統一:new(Type)
は、Type
型の新しいインスタンスを割り当て、そのゼロ値で初期化し、そのインスタンスへのポインタ (*Type
) を返します。これは、プリミティブ型、構造体、配列など、あらゆる型に一貫して適用されます。以前のnew(*Type)
のような記述は、new
が既にポインタを返すため冗長であり、このコミットによってnew(Type)
に統一されました。 -
make(Type, ...)
の導入と特化: スライス、マップ、チャネルは、単なるメモリ領域の確保以上の初期化を必要とする特殊な参照型です。- スライス:
make([]Type, length, capacity)
は、指定された長さと容量を持つ基盤となる配列を割り当て、その配列を参照するスライスヘッダ(ポインタ、長さ、容量)を初期化して返します。 - マップ:
make(map[Key]Value)
は、ハッシュテーブルの内部構造を初期化し、空のマップを返します。 - チャネル:
make(chan Type, capacity)
は、チャネルのバッファを初期化し、送受信が可能なチャネルを返します。
- スライス:
この分離により、Goのコードはより読みやすく、意図が明確になります。開発者は、new
を見れば「新しいゼロ値のインスタンスへのポインタ」を、make
を見れば「初期化されたスライス、マップ、チャネル」を期待できるようになります。これは、Go言語が「シンプルさ」と「明確さ」を重視する設計哲学の一環です。
関連リンク
- Go言語の公式ドキュメント:
- The Go Programming Language Specification - Allocation (現在の仕様)
- Effective Go - Allocation (現在のドキュメント)
参考にした情報源リンク
- Go言語のソースコード (GitHub)
- Go言語の公式ドキュメント
- Go言語の仕様
- Effective Go
- Go言語の初期のメーリングリストやデザインドキュメント(
new
とmake
の進化に関する議論を探すためにWeb検索を使用しました。具体的なURLは特定できませんでしたが、これらの概念が初期から議論され、現在の形に落ち着いた経緯が背景にあります。)new
とmake
の歴史的な経緯については、Go言語の初期の設計ドキュメントやメーリングリストのアーカイブに詳細が残されている可能性がありますが、このコミットの時点(2009年)ではまだ言語が非常に若く、ドキュメントも発展途上でした。しかし、この変更は、Go言語が目指す「シンプルで一貫性のある」設計原則を反映したものです。- 特に、Russ Cox氏による初期のGo言語に関するブログ記事やプレゼンテーションは、このような設計判断の背景を理解する上で非常に有用です。
new
とmake
の違いに関する議論は、Go言語の学習者にとって常に重要なトピックであり、多くのブログ記事やチュートリアルで解説されています。