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

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

このコミットは、Go言語の初期のコードベースにおけるメモリ割り当ての慣用的なパターンを修正するものです。具体的には、new(*Type) の形式を new(Type) に、そしてマップの初期化において new(map[Key]Value)make(map[Key]Value) に変更しています。これは、Go言語における型とマップの正しいインスタンス化と初期化の方法に準拠するための重要な変更です。影響を受けるファイルは、usr/gri/pretty/ast.gousr/gri/pretty/compilation.gousr/gri/pretty/globals.gousr/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言語における newmake という2つの組み込み関数の正しい使用法に関する理解の深化と、それに基づくコードベースの修正があります。Go言語の初期段階では、これらの関数のセマンティクスがまだ完全に確立されていなかったか、あるいは開発者間での慣用的な使用法が統一されていなかった可能性があります。

new 関数は、任意の型のゼロ値が格納された新しいメモリ領域を割り当て、その領域へのポインタを返します。一方、make 関数は、スライス、マップ、チャネルといった特定の組み込み参照型を初期化するために使用されます。これらの型は、new で割り当てられただけではゼロ値(マップの場合は nil)となり、そのままでは使用できません。特にマップの場合、nil マップに要素を追加しようとするとランタイムパニックが発生します。

このコミットは、pretty パッケージ(おそらくGoコードの整形や抽象構文木 (AST) の操作を行うツール)内で、これらの関数の誤った使用法を修正することを目的としています。具体的には、構造体やその他の型をインスタンス化する際に new(*Type) とポインタへのポインタを生成していた箇所を new(Type) に変更し、マップを初期化する際に new(map[Key]Value)nil マップへのポインタを生成していた箇所を make(map[Key]Value) に変更しています。これにより、コードの堅牢性と正確性が向上します。

前提知識の解説

Go言語の newmake 関数

Go言語には、メモリを割り当てて値を初期化するための2つの主要な組み込み関数 newmake があります。

  1. new(Type):

    • 任意の Type のゼロ値を格納するためのメモリを割り当てます。
    • 割り当てられたメモリへのポインタ (*Type) を返します。
    • 例: p := new(int)*p0int 型のポインタを返します。s := new(MyStruct)MyStruct の全フィールドがゼロ値で初期化されたポインタを返します。
    • new(*Type) のように書くと、Type へのポインタ (*Type) のゼロ値(つまり nil)を格納するためのメモリを割り当て、そのポインタへのポインタ (**Type) を返します。これは通常意図しない動作であり、このコミットで修正されている点です。
  2. 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言語の型システムとメモリ管理の初期設計における、newmake のセマンティクスに関する修正です。

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.goTokenStream メソッドでは、チャネルの初期化も 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言語における newmake の使用法を修正し、より正確で慣用的なメモリ割り当てと初期化を行うことです。

  1. 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 の新しいインスタンスを指すようになり、正しいオブジェクト生成が行われます。
  2. new(map[Key]Value) から make(map[Key]Value) への変更:

    • これは、compilation.goAddDeps 関数内の localsetComputeDeps 関数内の globalsetglobals.goNewScope 関数内の scope.entriesscanner.goinit 関数内の Keywords で行われています。
    • マップは参照型であり、new で割り当てると nil マップへのポインタが返されます。nil マップは使用できません。
    • make(map[Key]Value) は、マップを適切に初期化し、要素を追加できる状態にします。これにより、マップのランタイムパニックを防ぎ、正しく機能させることができます。
  3. new(chan Type, buffer) から make(chan Type, buffer) への変更:

    • scanner.goTokenStream メソッド内で、チャネルの初期化が修正されています。
    • チャネルもマップと同様に make で初期化する必要がある参照型です。new を使うと nil チャネルへのポインタが生成され、送受信操作がブロックされるかパニックを引き起こす可能性があります。
    • make を使うことで、バッファ付きチャネルが適切に初期化され、期待通りに動作します。

これらの変更は、Go言語の初期のコードベースにおいて、メモリ割り当てと参照型の初期化に関するベストプラクティスが確立されていく過程を示しており、Go言語の堅牢性と安全性を高める上で不可欠な修正でした。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語に関する技術ブログやチュートリアル (特に newmake の違いについて解説しているもの)
  • Gitの差分表示ツール (diff) の一般的な理解
  • 抽象構文木 (AST) とコンパイラの基本概念