[インデックス 1416] ファイルの概要
このコミットは、Go言語の初期のコードベースにおけるメモリ割り当ての慣用的なパターンを修正するものです。具体的には、new(*Type)
の形式を new(Type)
に、そしてマップの初期化において new(map[Key]Value)
を make(map[Key]Value)
に変更しています。これは、Go言語における型とマップの正しいインスタンス化と初期化の方法に準拠するための重要な変更です。影響を受けるファイルは、usr/gri/pretty/ast.go
、usr/gri/pretty/compilation.go
、usr/gri/pretty/globals.go
、usr/gri/pretty/scanner.go
であり、これらはGoコードの構文解析や整形に関連するコンポーネントの一部であると推測されます。
コミット
commit 9662e7b2db0fa8c2bb4d8cf28940116763eedbc9
Author: Robert Griesemer <gri@golang.org>
Date: Tue Jan 6 15:01:04 2009 -0800
- adjusted pretty to use old new, make
R=r
OCL=22160
CL=22160
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9662e7b2db0fa8c2bb4d8cf28940116763eedbc9
元コミット内容
- adjusted pretty to use old new, make
R=r
OCL=22160
CL=22160
変更の背景
このコミットの背景には、Go言語における new
と make
という2つの組み込み関数の正しい使用法に関する理解の深化と、それに基づくコードベースの修正があります。Go言語の初期段階では、これらの関数のセマンティクスがまだ完全に確立されていなかったか、あるいは開発者間での慣用的な使用法が統一されていなかった可能性があります。
new
関数は、任意の型のゼロ値が格納された新しいメモリ領域を割り当て、その領域へのポインタを返します。一方、make
関数は、スライス、マップ、チャネルといった特定の組み込み参照型を初期化するために使用されます。これらの型は、new
で割り当てられただけではゼロ値(マップの場合は nil
)となり、そのままでは使用できません。特にマップの場合、nil
マップに要素を追加しようとするとランタイムパニックが発生します。
このコミットは、pretty
パッケージ(おそらくGoコードの整形や抽象構文木 (AST) の操作を行うツール)内で、これらの関数の誤った使用法を修正することを目的としています。具体的には、構造体やその他の型をインスタンス化する際に new(*Type)
とポインタへのポインタを生成していた箇所を new(Type)
に変更し、マップを初期化する際に new(map[Key]Value)
と nil
マップへのポインタを生成していた箇所を make(map[Key]Value)
に変更しています。これにより、コードの堅牢性と正確性が向上します。
前提知識の解説
Go言語の new
と make
関数
Go言語には、メモリを割り当てて値を初期化するための2つの主要な組み込み関数 new
と make
があります。
-
new(Type)
:- 任意の
Type
のゼロ値を格納するためのメモリを割り当てます。 - 割り当てられたメモリへのポインタ (
*Type
) を返します。 - 例:
p := new(int)
は*p
が0
のint
型のポインタを返します。s := new(MyStruct)
はMyStruct
の全フィールドがゼロ値で初期化されたポインタを返します。 new(*Type)
のように書くと、Type
へのポインタ (*Type
) のゼロ値(つまりnil
)を格納するためのメモリを割り当て、そのポインタへのポインタ (**Type
) を返します。これは通常意図しない動作であり、このコミットで修正されている点です。
- 任意の
-
make(Type, size, capacity)
:- スライス、マップ、チャネルという特定の組み込み参照型のみを初期化するために使用されます。
- これらの型は、
new
で割り当てられただけではゼロ値(スライスやチャネルはnil
、マップもnil
)となり、そのままでは要素を追加したり、送受信したりすることができません。 make
は、これらの型が実際に使用できる状態になるように、必要な内部データ構造を初期化します。- 例:
s := make([]int, 5, 10)
: 長さ5、容量10のint
型のスライスを初期化します。m := make(map[string]int)
: 空のstring
キーとint
値のマップを初期化します。c := make(chan int, 10)
: バッファサイズ10のint
型のチャネルを初期化します。
Go言語におけるポインタ
Go言語のポインタは、変数のメモリアドレスを指し示します。C/C++のようなポインタ演算はできませんが、値渡しと参照渡しの区別、およびデータ構造の動的な割り当てに不可欠です。
&
演算子: 変数のアドレスを取得します。例:p := &x
*
演算子: ポインタが指す値を取得します(デリファレンス)。例:v := *p
抽象構文木 (AST) とスキャナ/パーサ
このコミットが影響する pretty
パッケージは、Go言語のソースコードを処理するツールの一部であると推測されます。
- スキャナ (Lexer): ソースコードを読み込み、トークン(キーワード、識別子、演算子など)のストリームに変換します。
- パーサ (Parser): トークンのストリームを読み込み、言語の文法規則に従って抽象構文木 (AST) を構築します。ASTは、プログラムの構造を木構造で表現したものです。
- AST (Abstract Syntax Tree): プログラムのソースコードの抽象的な構文構造を表現する木構造です。コンパイラやコード分析ツール、整形ツールなどで広く利用されます。
pretty
パッケージは、このASTを操作してコードを整形する役割を担っている可能性があります。
技術的詳細
このコミットの技術的な核心は、Go言語の型システムとメモリ管理の初期設計における、new
と make
のセマンティクスに関する修正です。
new(*Type)
から new(Type)
への変更
Go言語では、構造体やプリミティブ型などの「値型」のインスタンスをヒープに割り当ててそのポインタを得たい場合、new(Type)
を使用するのが正しい方法です。これは *Type
型のポインタを返します。
元のコード e := new(*Expr)
は、Expr
型へのポインタ (*Expr
) のゼロ値(つまり nil
)を格納するためのメモリを割り当て、そのメモリへのポインタ (**Expr
) を e
に代入していました。これは、Expr
型の新しいインスタンスを生成してそのポインタを得るという意図とは異なります。
修正後の e := new(Expr)
は、Expr
型のゼロ値が格納されたメモリを割り当て、そのメモリへのポインタ (*Expr
) を e
に代入します。これにより、e
は直接 Expr
型のインスタンスを指すようになり、意図した通りのオブジェクト生成が行われます。
この変更は、ast.go
内の NewExpr
, NewLit
, NewType
, NewTypeExpr
, NewStat
, NewDecl
, NewComment
, NewProgram
関数、globals.go
内の NewObject
, NewType
, NewPackage
, NewScope
, Copy
メソッド、scanner.go
内の TokenStream
メソッドなど、多くのオブジェクト生成関数に適用されています。
new(map[Key]Value)
から make(map[Key]Value)
への変更
Go言語のマップは参照型であり、使用する前に必ず make
関数で初期化する必要があります。new(map[Key]Value)
は map[Key]Value
型のゼロ値(nil
マップ)を格納するためのメモリを割り当て、そのポインタ (*map[Key]Value
) を返します。しかし、nil
マップは要素を追加したり、削除したりする操作ができません。これらの操作を行うには、make
を使ってマップを初期化し、内部のハッシュテーブル構造を構築する必要があります。
元のコード localset := new(map [string] bool)
や globalset := new(map [string] bool)
、Keywords = new(map [string] int)
は、nil
マップへのポインタを生成していました。これでは、その後にマップに要素を追加しようとするとランタイムパニックが発生するか、意図しない動作を引き起こす可能性があります。
修正後の localset := make(map [string] bool)
や globalset := make(map [string] bool)
、Keywords = make(map [string] int)
は、空のマップを適切に初期化し、すぐに要素を追加できる状態にします。これにより、マップの正しい利用が可能になります。
この変更は、compilation.go
内の AddDeps
関数、globals.go
内の NewScope
関数、scanner.go
内の init
関数に適用されています。
チャネルの初期化
scanner.go
の TokenStream
メソッドでは、チャネルの初期化も new(chan *Token, 100)
から make(chan *Token, 100)
に変更されています。チャネルもマップと同様に make
で初期化する必要がある参照型です。new
を使うと nil
チャネルへのポインタが生成され、送受信操作がブロックされるかパニックを引き起こす可能性があります。make
を使うことで、バッファ付きチャネルが適切に初期化され、期待通りに動作します。
これらの変更は、Go言語の設計思想とメモリモデルが固まっていく過程で、より安全で慣用的なコーディングスタイルを確立するための重要なステップであったと言えます。
コアとなるコードの変更箇所
usr/gri/pretty/ast.go
--- a/usr/gri/pretty/ast.go
+++ b/usr/gri/pretty/ast.go
@@ -56,14 +56,14 @@ export func NewExpr(pos, tok int, x, y *Expr) *Expr {
if x != nil && x.tok == Scanner.TYPE || y != nil && y.tok == Scanner.TYPE {
panic("no type expression allowed");
}
- e := new(*Expr);
+ e := new(Expr);
e.pos, e.tok, e.x, e.y = pos, tok, x, y;
return e;
}
export func NewLit(pos, tok int, s string) *Expr {
- e := new(*Expr);
+ e := new(Expr);
e.pos, e.tok, e.s = pos, tok, s;
return e;
}
@@ -112,7 +112,7 @@ func (t *Type) nfields() int {
export func NewType(pos, tok int) *Type {
- t := new(*Type);
+ t := new(Type);
t.pos, t.tok = pos, tok;
return t;
}
@@ -120,7 +120,7 @@ export func NewType(pos, tok int) *Type {
// requires complete Type type
export func NewTypeExpr(t *Type) *Expr {
- e := new(*Expr);
+ e := new(Expr);
e.pos, e.tok, e.t = t.pos, Scanner.TYPE, t;
return e;
}
@@ -142,7 +142,7 @@ export type Stat struct {
export func NewStat(pos, tok int) *Stat {
- s := new(*Stat);
+ s := new(Stat);
s.pos, s.tok = pos, tok;
return s;
}
@@ -167,7 +167,7 @@ export type Decl struct {
export func NewDecl(pos, tok int, exported bool) *Decl {
- d := new(*Decl);
+ d := new(Decl);
d.pos, d.tok, d.exported = pos, tok, exported;
return d;
}
@@ -186,7 +186,7 @@ export type Comment struct {
export func NewComment(pos int, text string) *Comment {
- c := new(*Comment);
+ c := new(Comment);
c.pos, c.text = pos, text;
return c;
}
@@ -201,7 +201,7 @@ export type Program struct {
export func NewProgram(pos int) *Program {
- p := new(*Program);
+ p := new(Program);
p.pos = pos;
return p;
}
usr/gri/pretty/compilation.go
--- a/usr/gri/pretty/compilation.go
+++ b/usr/gri/pretty/compilation.go
@@ -167,7 +167,7 @@ func AddDeps(globalset map [string] bool, wset *array.Array, src_file string, fl
if nimports > 0 {
print(src_file, ".6:\t");
- localset := new(map [string] bool);
+ localset := make(map [string] bool);
for i := 0; i < nimports; i++ {
decl := prog.decls.At(i).(*AST.Decl);
assert(decl.tok == Scanner.IMPORT && decl.val.tok == Scanner.STRING);
@@ -198,7 +198,7 @@ func AddDeps(globalset map [string] bool, wset *array.Array, src_file string, fl
export func ComputeDeps(src_file string, flags *Flags) {
- globalset := new(map [string] bool);
+ globalset := make(map [string] bool);
wset := array.New(0);
wset.Push(src_file);
for wset.Len() > 0 {
usr/gri/pretty/globals.go
--- a/usr/gri/pretty/globals.go
+++ b/usr/gri/pretty/globals.go
@@ -119,7 +119,7 @@ export type Elem struct {
export var Universe_void_typ *Type // initialized by Universe to Universe.void_typ
export func NewObject(pos, kind int, ident string) *Object {
- obj := new(*Object);
+ obj := new(Object);
obj.exported = false;
obj.pos = pos;
obj.kind = kind;
@@ -131,7 +131,7 @@ export func NewObject(pos, kind int, ident string) *Object {
export func NewType(form int) *Type {
- typ := new(*Type);
+ typ := new(Type);
typ.ref = -1; // not yet exported
typ.form = form;
return typ;
@@ -139,7 +139,7 @@ export func NewType(form int) *Type {
export func NewPackage(file_name string, obj *Object, scope *Scope) *Package {
- pkg := new(*Package);
+ pkg := new(Package);
pkg.ref = -1; // not yet exported
pkg.file_name = file_name;
pkg.key = "<the package key>"; // empty key means package forward declaration
@@ -150,9 +150,9 @@ export func NewPackage(file_name string, obj *Object, scope *Scope) *Package {
export func NewScope(parent *Scope) *Scope {
- scope := new(*Scope);
+ scope := new(Scope);
scope.parent = parent;
- scope.entries = new(map[string]*Object, 8);
+ scope.entries = make(map[string]*Object, 8);
return scope;
}
@@ -161,7 +161,7 @@ export func NewScope(parent *Scope) *Scope {
// Object methods
func (obj *Object) Copy() *Object {
- copy := new(*Object);
+ copy := new(Object);
copy.exported = obj.exported;
copy.pos = obj.pos;
copy.kind = obj.kind;
usr/gri/pretty/scanner.go
--- a/usr/gri/pretty/scanner.go
+++ b/usr/gri/pretty/scanner.go
@@ -246,7 +246,7 @@ var Keywords map [string] int;
func init() {
- Keywords = new(map [string] int);
+ Keywords = make(map [string] int);
for i := KEYWORDS_BEG + 1; i < KEYWORDS_END; i++ {
Keywords[TokenString(i)] = i;
}
@@ -759,10 +759,10 @@ export type Token struct {
func (S *Scanner) TokenStream() <-chan *Token {
- ch := new(chan *Token, 100);
+ ch := make(chan *Token, 100);
go func(S *Scanner, ch chan <- *Token) {
for {
- t := new(*Token);
+ t := new(Token);
t.pos, t.tok, t.val = S.Scan();
ch <- t;
if t.tok == EOF {
コアとなるコードの解説
このコミットの主要な変更は、Go言語における new
と make
の使用法を修正し、より正確で慣用的なメモリ割り当てと初期化を行うことです。
-
new(*Type)
からnew(Type)
への変更:- これは、
Expr
,Lit
,Type
,Stat
,Decl
,Comment
,Program
といったASTノードや、Object
,Package
,Scope
といったグローバルオブジェクトのファクトリ関数 (New...
関数) で行われています。 - 元の
new(*Type)
は、Type
型へのポインタ (*Type
) のゼロ値(つまりnil
)を格納するためのメモリを割り当て、そのメモリへのポインタ (**Type
) を返していました。これは、新しいType
のインスタンスを生成してそのポインタを得るという意図とは異なります。 - 修正後の
new(Type)
は、Type
型のゼロ値が格納されたメモリを割り当て、そのメモリへのポインタ (*Type
) を返します。これにより、返されるポインタは直接Type
の新しいインスタンスを指すようになり、正しいオブジェクト生成が行われます。
- これは、
-
new(map[Key]Value)
からmake(map[Key]Value)
への変更:- これは、
compilation.go
のAddDeps
関数内のlocalset
、ComputeDeps
関数内のglobalset
、globals.go
のNewScope
関数内のscope.entries
、scanner.go
のinit
関数内のKeywords
で行われています。 - マップは参照型であり、
new
で割り当てるとnil
マップへのポインタが返されます。nil
マップは使用できません。 make(map[Key]Value)
は、マップを適切に初期化し、要素を追加できる状態にします。これにより、マップのランタイムパニックを防ぎ、正しく機能させることができます。
- これは、
-
new(chan Type, buffer)
からmake(chan Type, buffer)
への変更:scanner.go
のTokenStream
メソッド内で、チャネルの初期化が修正されています。- チャネルもマップと同様に
make
で初期化する必要がある参照型です。new
を使うとnil
チャネルへのポインタが生成され、送受信操作がブロックされるかパニックを引き起こす可能性があります。 make
を使うことで、バッファ付きチャネルが適切に初期化され、期待通りに動作します。
これらの変更は、Go言語の初期のコードベースにおいて、メモリ割り当てと参照型の初期化に関するベストプラクティスが確立されていく過程を示しており、Go言語の堅牢性と安全性を高める上で不可欠な修正でした。
関連リンク
- Go言語の
new
とmake
の違いに関する公式ドキュメントやブログ記事:
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語に関する技術ブログやチュートリアル (特に
new
とmake
の違いについて解説しているもの) - Gitの差分表示ツール (diff) の一般的な理解
- 抽象構文木 (AST) とコンパイラの基本概念