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

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

このコミットは、Go言語の公式仕様書である doc/go_spec.html ファイルに対して行われた変更です。主な目的は、Goプログラムにおけるパッケージの初期化に関するセクションの記述を明確化し、より正確な情報を提供することにあります。特に、パッケージレベル変数の初期化順序、依存関係の定義、そしてinit関数の振る舞いについて、既存の挙動をより厳密に記述しています。これは言語仕様の変更ではなく、既存の言語のセ振る舞いに関する記述の明確化です。

コミット

commit a43669843b155ddb575d95acdb72dc62a1434efd
Author: Robert Griesemer <gri@golang.org>
Date:   Tue May 20 13:51:39 2014 -0700

    spec: clarify section on package initialization
    
    - split description of package initialization and
      program execution
    - better grouping of concerns in section on package
      initialization
    - more explicit definition of what constitues a
      dependency
    - removed language about constant dependencies -
      they are computed at compile-time and not
      initialized at run-time
    - clarified that independent variables are initialized
      in declaration order (rather than reference order)
    
    Note that the last clarification is what distinguishes
    gc and gccgo at the moment: gc uses reference order
    (i.e., order in which variables are referenced in
    initialization expressions), while gccgo uses declaration
    order for independent variables.
    
    Not a language change. But adopting this CL will
    clarify what constitutes a dependency.
    
    Fixes #6703.
    
    LGTM=adonovan, r, iant, rsc
    R=r, rsc, iant, ken, adonovan
    CC=golang-codereviews
    https://golang.org/cl/99020043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/a43669843b155ddb575d95acdb72dc62a1434efd

元コミット内容

このコミットの目的は、Go言語仕様の「パッケージ初期化」セクションを明確にすることです。具体的には以下の点が変更されました。

  • パッケージ初期化とプログラム実行に関する記述を分離しました。
  • パッケージ初期化セクション内の懸念事項のグループ化を改善しました。
  • 依存関係を構成する要素について、より明示的な定義を行いました。
  • 定数依存関係に関する記述を削除しました。定数はコンパイル時に計算されるため、実行時に初期化されるものではないためです。
  • 独立した変数(互いに依存しない変数)が、参照順ではなく宣言順に初期化されることを明確にしました。

最後の明確化は、当時のgc(Goコンパイラ)とgccgo(GCCベースのGoコンパイラ)の挙動の違いを区別するものでした。gcは参照順を使用していたのに対し、gccgoは独立した変数に対して宣言順を使用していました。

これは言語の変更ではなく、この変更リスト(CL)を適用することで、依存関係が何を構成するのかが明確になります。

このコミットはIssue #6703を修正します。

変更の背景

Go言語の設計思想の一つに「明確さ」と「予測可能性」があります。しかし、初期のGo言語仕様では、パッケージの初期化順序、特に複数のパッケージレベル変数が存在し、それらが互いに直接的な依存関係を持たない場合の挙動について、曖昧な点が残されていました。

具体的には、Goの主要なコンパイラ実装であるgcと、GCCをバックエンドとするgccgoの間で、独立したパッケージレベル変数の初期化順序に差異がありました。gcは変数が初期化式で参照される順序(参照順)で初期化する傾向があったのに対し、gccgoはソースコード上での宣言順で初期化していました。このようなコンパイラ実装間の挙動の不一致は、Goプログラムの移植性や予測可能性を損なう可能性がありました。

このコミットは、このような曖昧さを解消し、Go言語仕様をより厳密に定義することを目的としています。言語の挙動そのものを変更するのではなく、既存の挙動のうち、どちらが「正しい」と見なされるべきかを明確にし、将来的なコンパイラ実装がそれに従うべき指針を示すものです。これにより、Goプログラムの挙動がより一貫性のあるものとなり、開発者が初期化順序に起因する予期せぬバグに遭遇するリスクを低減します。

また、パッケージ初期化とプログラム実行という異なる概念が同じセクションで扱われていたため、それぞれの役割とタイミングが混同される可能性がありました。これを分離することで、仕様書の可読性と理解度を向上させる狙いもありました。

前提知識の解説

このコミットの変更内容を理解するためには、以下のGo言語の概念について理解しておく必要があります。

1. Goのパッケージ初期化 (Package Initialization)

Goプログラムは、mainパッケージのmain関数が実行される前に、関連するすべてのパッケージが初期化されるという厳格な順序を持っています。この初期化プロセスは以下のステップで構成されます。

  • インポートされたパッケージの初期化: あるパッケージが他のパッケージをインポートしている場合、インポートされたパッケージが先に初期化されます。このプロセスは再帰的に行われ、依存関係のツリーを深く辿っていきます。循環インポートはコンパイルエラーとなります。
  • パッケージレベル変数の初期化: 各パッケージ内で宣言されたパッケージレベル変数(グローバル変数に相当)は、その初期化式に基づいて値が割り当てられます。
  • init関数の実行: 各パッケージは、引数も戻り値もない特別な関数initを複数定義できます。これらのinit関数は、パッケージレベル変数の初期化が完了した後に、定義された順序とは関係なく(Go言語仕様では「unspecified order」とされていますが、実際にはファイル内の出現順やコンパイラの実装に依存することが多い)、実行されます。

この初期化プロセスは、単一のゴルーチン内で順次実行され、init関数が他のゴルーチンを起動しても、そのinit関数が完了するまで次の初期化ステップは開始されません。

2. パッケージレベル変数 (Package-level variables)

関数やメソッドの外部、パッケージスコープで宣言される変数です。これらはプログラムの実行開始時に一度だけ初期化されます。

例:

package main

var (
    a = 1
    b = "hello"
)

func main() {
    // ...
}

3. init 関数

Go言語の特別な関数で、パッケージが初期化される際に自動的に実行されます。

  • 引数も戻り値も持ちません (func init())。
  • パッケージ内に複数定義できます。
  • 明示的に呼び出すことはできません。
  • パッケージレベル変数の初期化が完了した後に実行されます。
  • 主に、パッケージの初期設定、データの検証、外部リソースの準備などに使用されます。

4. 依存関係 (Dependencies)

Goのパッケージ初期化における依存関係は、ある変数の初期化が他の変数や関数の値に依存している場合に発生します。例えば、var x = y + 1 のような場合、xyに依存します。この依存関係は、初期化順序を決定する上で非常に重要です。循環依存はコンパイルエラーとなります。

5. gc (Go compiler) と gccgo (GCC-based Go compiler)

  • gc: Go言語の公式かつ主要なコンパイラ実装です。Goのソースコードを直接機械語にコンパイルします。Go開発チームによって開発・保守されています。
  • gccgo: GCC(GNU Compiler Collection)をバックエンドとして使用するGoコンパイラです。GoのソースコードをGCCの中間表現に変換し、その後GCCが機械語にコンパイルします。gcとは異なる最適化やコード生成戦略を持つため、特定のケースで挙動が異なることがありました。

このコミットの背景にある問題は、これら二つの主要なコンパイラ実装の間で、Go言語仕様の曖昧な部分に対する解釈が異なり、結果として独立したパッケージレベル変数の初期化順序に差異が生じていた点にあります。

6. Go言語仕様 (Go Language Specification)

Go言語の公式な定義であり、言語の構文、セマンティクス、標準ライブラリの振る舞いなどを記述しています。Goコンパイラやツールは、この仕様に厳密に従って実装されるべきです。仕様の明確化は、Go言語のエコシステム全体の一貫性と安定性を保つ上で極めて重要です。

技術的詳細

このコミットは、Go言語仕様のdoc/go_spec.htmlファイルにおける「Order of evaluation」および「Program execution」セクションを大幅に改訂し、特にパッケージ初期化のメカニズムに関する記述を詳細化・明確化しています。

1. 「パッケージ初期化」と「プログラム実行」の分離と再構成

以前は「Program execution」という一つのセクションで、パッケージ初期化とプログラム全体の実行(main関数の呼び出しなど)が混在して記述されていました。この変更により、これらが明確に分離され、新たにid="Package_initialization"というセクションが設けられました。これにより、それぞれの概念が独立して理解しやすくなりました。

2. 依存関係の定義の明確化

最も重要な変更点の一つは、パッケージレベル変数の初期化における「依存関係」の定義がより厳密になったことです。

  • 定数依存関係の削除: 以前の仕様には「constant dependencies」という記述がありましたが、定数はコンパイル時に計算される値であり、実行時の初期化プロセスには関与しないため、この記述は削除されました。これにより、初期化の概念がより純粋に実行時の変数割り当てに限定されました。
  • 参照に基づく依存関係の定義:
    • 変数の初期化式や関数/メソッドの本体が、他の変数、関数、またはメソッドを参照している場合に依存関係が発生することが明示されました。
    • 特に、メソッド値 (t.m) やメソッド式 (T.m) の参照も依存関係を形成することが明確にされました。これは、そのメソッドが実際に呼び出されるかどうかに関わらず、参照が存在するだけで依存関係が成立することを示しています。
    • 依存関係の分析はパッケージごとに行われ、現在のパッケージ内で宣言された要素への参照のみが考慮されます。
    • 変数間の循環依存はエラーとなることが改めて強調されました。

3. 独立した変数の初期化順序の明確化

このコミットの核心的な変更は、互いに依存しないパッケージレベル変数は、ソースコード上での宣言順に初期化されるという点が明確にされたことです。

以前の仕様では、この点について曖昧さがあり、gcコンパイラが参照順(初期化式で変数が参照される順序)で初期化する傾向があったのに対し、gccgoコンパイラは宣言順で初期化していました。この仕様の明確化により、Go言語の公式な振る舞いとして「宣言順」が採用され、コンパイラ実装間の差異を解消し、Goプログラムの予測可能性と移植性を高めることが意図されています。

この変更を説明するために、以下の具体的なコード例が仕様に追加されました。

var (
    a = c + b
    b = f()
    c = f()
    d = 3
)

func f() int {
    d++
    return d
}

この例において、初期化順序は d, b, c, a となります。bcは互いに独立しているため、宣言順(bcより前)に初期化されることが明示されています。

4. init関数の記述の整理

init関数に関する記述も、新しい「Package initialization」セクション内に再配置され、その特性がより簡潔にまとめられました。

  • init関数はinit識別子を宣言しないため、プログラムのどこからも参照できないことが強調されました。
  • パッケージレベル変数の初期化が完了した後に、すべてのinit関数が「unspecified order」(順序不定)で実行されることが改めて述べられました。

5. 初期化のシーケンシャルな性質の強調

パッケージ初期化(変数初期化とinit関数の呼び出し)は、単一のゴルーチン内で、一度に1つのパッケージずつ、順次(sequentially)行われることが明確にされました。init関数が他のゴルーチンを起動しても、そのinit関数が戻るまで次の初期化ステップは開始されないという挙動が強調されています。

これらの変更は、Go言語の初期化セマンティクスをより堅牢で理解しやすいものにし、異なるコンパイラ実装間での一貫性を保証するための重要なステップでした。

コアとなるコードの変更箇所

このコミットによる変更は、doc/go_spec.html ファイルに集中しています。

--- a/doc/go_spec.html
+++ b/doc/go_spec.html
@@ -1,6 +1,6 @@
 <!--{
  	"Title": "The Go Programming Language Specification",
- 	"Subtitle": "Version of May 19, 2014",
+ 	"Subtitle": "Version of May 20, 2014",
  	"Path": "/ref/spec"
 }-->
 
@@ -1533,6 +1533,9 @@ no identifier may be declared in both the file and package block.
 <p>
 The <a href="#Blank_identifier">blank identifier</a> may be used like any other identifier
 in a declaration, but it does not introduce a binding and thus is not declared.
+In the package block, the identifier <code>init</code> may only be used for
+<a href="#Package_initialization"><code>init</code> function</a> declarations,
+and like the blank identifier it does not introduce a new binding.
 </p>
 
 <pre class="ebnf">
@@ -4014,7 +4017,7 @@ precision.
 <h3 id="Order_of_evaluation">Order of evaluation</h3>
 
 <p>
-At package level, <a href="#Program_execution">initialization dependencies</a>
+At package level, <a href="#Package_initialization">initialization dependencies</a>
 determine the evaluation order of individual initialization expressions in
 <a href="#Variable_declarations">variable declarations</a>.
 Otherwise, when evaluating the <a href="#Operands">operands</a> of an
@@ -5907,62 +5910,125 @@ The same would also be true after
 var t T
 </pre>
 
-<h3 id="Program_execution">Program execution</h3>
+<h3 id="Package_initialization">Package initialization</h3>
+<p>
+Within a package, package-level variables are initialized according
+to their <i>dependencies</i>: if a variable <code>x</code> depends on
+a variable <code>y</code>, <code>x</code> will be initialized after
+<code>y</code>.
+</p>
+<p>
+Dependency analysis does not rely on the actual values of the
+variables, only on lexical <i>references</i> to them in the source,
+analyzed transitively. For instance, a variable <code>x</code>'s
+<a href="#Variable_declarations">initialization expression</a>
+may refer to a function whose body refers to variable <code>y</code>;
+if so, <code>x</code> depends on <code>y</code>.
+Specifically:
+</p>
+<ul>
+<li>
+A reference to a variable or function is an identifier denoting that
+variable or function.
+</li>
+<li>
+A reference to a method <code>m</code> is a
+<a href="#Method_values">method value</a> or
+<a href="#Method_expressions">method expression</a> of the form 
+<code>t.m</code>, where the (static) type of <code>t</code> is
+not an interface type, and the method <code>m</code> is in the
+<a href="#Method_sets">method set</a> of <code>t</code>.
+It is immaterial whether the resulting function value
+<code>t.m</code> is invoked.
+</li>
+<li>
+A variable, function, or method <code>x</code> depends on a variable 
+<code>y</code> if <code>x</code>'s initialization expression or body
+(for functions and methods) contains a reference to <code>y</code>
+or to a function or method that depends on <code>y</code>.
+</li>
+</ul>
+<p>
+Dependency analysis is performed per package; only references referring
+to variables, functions, and methods declared in the current package
+are considered.
+It is an error if variable dependencies form a cycle
+(but dependency cycles containing no variables are permitted).
+If two variables are independent of each other,
+they are initialized in the order they are declared
+in the source, possibly in multiple files, as presented to the compiler.
+</p>
+<p>
+For example, given the declarations
+</p>
+<pre>
+var (
+	a = c + b
+	b = f()
+	c = f()
+	d = 3
+)
+
+func f() int {
+	d++
+	return d
+}
+</pre>
+<p>
+the initialization order is <code>d</code>, <code>b</code>, <code>c</code>, <code>a</code>.
+Since <code>b</code> and <code>c</code> are independent of each other, they are
+initialized in declaration order (<code>b</code> before <code>c</code>).
+</p>
+<p>
+Variables may also be initialized using functions named <code>init</code>
+declared in the package block, with no arguments and no result parameters.
+</p>
+<pre>
+func init() { … }
+</pre>
+<p>
+Multiple such functions may be defined, even within a single 
+source file. The <code>init</code> identifier is not
+<a href="#Declarations_and_scope">declared</a> and thus
+<code>init</code> functions cannot be referred to from anywhere
+in a program.
+</p>
+<p>
+A package with no imports is initialized by assigning initial values
+to all its package-level variables followed by calling all <code>init</code>
+functions in unspecified order.
+If a package has imports, the imported packages are initialized
+before initializing the package itself. If multiple packages import
+-a package <code>P</code>, <code>P</code> will be initialized only once.\n+a package, the imported package will be initialized only once.
+The importing of packages, by construction, guarantees that there
+can be no cyclic initialization dependencies.
+</p>
+<p>
+Package initialization&mdash;variable initialization and the invocation of
+<code>init</code> functions&mdash;happens in a single goroutine,
+sequentially, one package at a time.
+An <code>init</code> function may launch other goroutines, which can run
+concurrently with the initialization code. However, initialization
+always sequences
+the <code>init</code> functions: it will not invoke the next one
+until the previous one has returned.
+</p>
+
+
+<h3 id="Program_execution">Program execution</h3>
 <p>
 A complete program is created by linking a single, unimported package
 called the <i>main package</i> with all the packages it imports, transitively.
@@ -5983,18 +6049,6 @@ When that function invocation returns, the program exits.\n It does not wait for other (non-<code>main</code>) goroutines to complete.\n </p>\n \n-<p>\n-Package initialization&mdash;variable initialization and the invocation of\n-<code>init</code> functions&mdash;happens in a single goroutine,\n-sequentially, one package at a time.\n-An <code>init</code> function may launch other goroutines, which can run\n-concurrently with the initialization code. However, initialization\n-always sequences\n-the <code>init</code> functions: it will not start the next\n-<code>init</code> until\n-the previous one has returned.\n-</p>\n-\n <h2 id="Errors">Errors</h2>
 
 <p>

主な変更点は以下の通りです。

  1. Subtitleの更新: 仕様書のバージョン日付が「May 19, 2014」から「May 20, 2014」に更新されています。
  2. init識別子に関する記述の追加: id="Declarations_and_scope"セクション(差分には含まれていませんが、関連する箇所)に、init識別子がinit関数宣言にのみ使用され、ブランク識別子と同様に新しいバインディングを導入しないことが追記されました。
  3. セクションの再構成:
    • 以前のid="Program_execution"セクションの内容が大幅に削除され、その多くが新しいid="Package_initialization"セクションに移動・再構成されました。
    • id="Program_execution"セクションは、プログラム全体の実行フロー(main関数の呼び出しなど)に特化した内容に簡素化されました。
  4. id="Package_initialization"セクションの新規追加と詳細化:
    • パッケージレベル変数が「依存関係」に基づいて初期化されることが明記されました。
    • 依存関係の分析が、変数の実際の値ではなく、ソースコード内の「語彙的な参照」に基づいて行われることが詳細に説明されました。
    • 依存関係を構成する具体的なルールが箇条書きで定義されました。
      • 変数や関数への参照。
      • メソッド値 (t.m) やメソッド式 (T.m) への参照。
      • 変数、関数、またはメソッドxが、その初期化式または本体(関数やメソッドの場合)にyへの参照、またはyに依存する関数やメソッドへの参照を含む場合にyに依存すること。
    • 依存関係分析はパッケージごとに行われ、現在のパッケージ内で宣言された要素への参照のみが考慮されること。
    • 変数間の循環依存はエラーであること(ただし、変数を含まない依存関係の循環は許可されること)。
    • 互いに独立した変数は、ソースコード上での宣言順に初期化されるという重要なルールが明確に記述されました。
    • このルールを説明するための具体的なコード例(a, b, c, dの初期化例)が追加されました。
    • init関数による変数初期化の可能性が述べられ、init関数の特性(複数定義可能、参照不可)が再確認されました。
    • インポートされたパッケージの初期化順序と、循環初期化依存がないことの保証が再確認されました。
    • パッケージ初期化が単一のゴルーチン内で順次行われること、およびinit関数が他のゴルーチンを起動しても、そのinit関数が戻るまで次のinit関数が呼び出されないことが強調されました。

コアとなるコードの解説

このコミットの核心は、Go言語仕様の「パッケージ初期化」に関する記述を、より厳密かつ包括的に定義し直した点にあります。

1. gcgccgoの挙動の統一

最も重要な変更は、独立したパッケージレベル変数の初期化順序に関する記述です。以前は、この点に関してgcgccgoの間で挙動の差異があり、gcが参照順、gccgoが宣言順で初期化していました。このコミットにより、Go言語仕様として「独立した変数は宣言順に初期化される」というルールが明確に定められました。これにより、Goプログラムの初期化挙動がコンパイラ実装に依存せず、一貫性を持つことが保証されます。

追加された以下のコード例は、このルールを具体的に示しています。

var (
	a = c + b
	b = f()
	c = f()
	d = 3
)

func f() int {
	d++
	return d
}

この例では、dは定数で初期化され、bcf()の呼び出し結果に依存し、abcに依存します。bcは互いに直接的な依存関係がありません。この場合、仕様の新しい記述に従い、bcより先に宣言されているため、bcより先に初期化されます。したがって、初期化順序は d, b, c, a となります。これは、f()が呼び出されるたびにdがインクリメントされるため、bcの値が異なる可能性があるという点で重要です。

2. 依存関係の厳密な定義

「依存関係」の定義が大幅に強化されました。特に、定数依存関係の概念が削除されたことは、初期化プロセスが実行時の変数割り当てに焦点を当てるべきであることを明確にしています。また、メソッド値やメソッド式への参照も依存関係を形成するという記述は、より複雑な初期化パターンにおける依存関係の検出を可能にし、仕様の網羅性を高めています。

3. init関数の位置づけの明確化

init関数に関する記述が「Package initialization」セクション内に統合され、その役割と制約がより明確になりました。init識別子が宣言を導入しないという点は、init関数が通常の関数とは異なる特別な存在であることを強調し、誤用を防ぐのに役立ちます。また、init関数が単一ゴルーチン内で順次実行されるという記述は、並行処理を伴う初期化コードを書く際の重要な指針となります。

これらの変更は、Go言語の初期化モデルをより堅牢で理解しやすいものにし、開発者がGoプログラムの起動時の挙動をより正確に予測できるようにするための基盤を築きました。

関連リンク

参考にした情報源リンク