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

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

このコミットは、Go言語の仕様書(doc/go_spec.html)におけるinit()関数の呼び出し順序に関する記述を明確にするものです。具体的には、これまで不明確だったinit()関数の実行順序を「ソースコード上の出現順」と定めることで、言語の挙動をより厳密に定義しています。

コミット

commit c00043b5d8bd53130bddb5ef1e88643dccc4586f
Author: Robert Griesemer <gri@golang.org>
Date:   Tue May 20 17:46:08 2014 -0700

    spec: specify order of init() calls
    
    The spec did not specify the order in which
    init() functions are called. Specify that
    they are called in source order since we have
    now also specified the initialization order
    of independent variables.
    
    While technically a language change, no
    existing code could have relied on this,
    so this should not break anything.
    
    Per suggestion from rsc.
    
    LGTM=r, iant
    R=rsc, iant, r, ken
    CC=golang-codereviews
    https://golang.org/cl/98420046

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

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

元コミット内容

このコミットの元の内容は、Go言語の仕様書においてinit()関数の呼び出し順序が明記されていなかった点を修正することです。具体的には、init()関数がソースコード上に出現する順序で呼び出されることを明確に規定しています。これは、独立した変数の初期化順序が既に指定されていることと整合性を取るための変更です。

変更の背景

Go言語では、パッケージの初期化時にinit()関数が実行されます。しかし、このコミット以前の仕様では、複数のinit()関数が存在する場合の正確な実行順序が明確に定義されていませんでした。特に、同じパッケージ内に複数のファイルがあり、それぞれにinit()関数が定義されている場合、その実行順序はコンパイラに渡されるファイルの順序やファイル名の辞書順に依存する可能性があり、厳密な保証がありませんでした。

Go言語の設計哲学は、明確で予測可能な挙動を提供することにあります。init()関数の実行順序が不明確であることは、開発者が初期化ロジックを記述する際に予期せぬ挙動に遭遇するリスクをはらんでいました。例えば、あるinit()関数が別のinit()関数によって初期化されるリソースに依存している場合、実行順序が保証されないと競合状態やデッドロックが発生する可能性がありました。

このコミットは、Go言語の設計者の一人であるRobert Griesemer氏によって提案され、rsc(Russ Cox氏)の示唆に基づいています。これは、Go言語の成熟に伴い、より厳密な言語仕様が求められるようになった背景を反映しています。独立した変数の初期化順序が既に明確に定義されていたため、init()関数の順序も同様に明確にすることで、言語全体の初期化セマンティクスの一貫性を高める狙いがありました。

前提知識の解説

このコミットの理解には、以下のGo言語の概念に関する知識が不可欠です。

1. init()関数

Go言語のinit()関数は、パッケージが初期化される際に自動的に実行される特殊な関数です。主な特徴は以下の通りです。

  • 自動実行: main()関数が呼び出される前に、関連するパッケージのinit()関数がすべて実行されます。
  • 引数なし、戻り値なし: func init()という形式で定義され、引数も戻り値も持ちません。
  • 複数定義可能: 1つのパッケージ内に複数のinit()関数を定義できます。同じファイル内、または異なるファイルに分散して定義することが可能です。
  • 呼び出し不可: ユーザーコードから明示的にinit()関数を呼び出すことはできません。
  • 用途: 主に、パッケージレベルの変数の複雑な初期化、外部リソースへの接続、設定ファイルの読み込み、登録処理など、プログラムの実行開始前に一度だけ実行する必要があるセットアップ処理に使用されます。

2. パッケージの初期化順序

Goプログラムの実行は、パッケージの初期化から始まります。その順序は以下のルールに従います。

  • 依存関係の解決: インポートされたパッケージは、そのパッケージをインポートするパッケージよりも先に初期化されます。このプロセスは再帰的に行われ、依存関係のツリーを深く辿って初期化が進みます。
  • 一度だけ実行: 各パッケージの初期化は、プログラムの実行中に一度だけ行われます。たとえ複数のパッケージから同じパッケージがインポートされていても、そのパッケージの初期化は一度しか行われません。
  • 初期化のステップ: 各パッケージの初期化は、以下の順序で行われます。
    1. パッケージレベルの変数の初期化。
    2. init()関数の実行。

3. 変数の初期化順序

Go言語では、パッケージレベルの変数の初期化順序も明確に定義されています。

  • 宣言順: 基本的に、パッケージレベルの変数はソースコード上での宣言順に初期化されます。
  • 依存関係: 変数の初期化式が他の変数に依存している場合、依存先の変数が先に初期化されます。これにより、循環参照がある場合はコンパイルエラーとなります。
  • 独立した変数: 依存関係を持たない(または依存関係が解決済みの)変数は、「独立した変数」として扱われ、その初期化順序もソースコード上の出現順に厳密に従います。

このコミットは、init()関数の実行順序を、この「独立した変数の初期化順序」と同様に「ソースコード上の出現順」とすることで、Go言語の初期化セマンティクス全体の一貫性を高めるものです。

技術的詳細

このコミットの技術的な核心は、Go言語の仕様書(doc/go_spec.html)におけるinit()関数の実行順序に関する記述を、曖昧な表現から明確な「ソースコード上の出現順」へと変更した点にあります。

変更前は、init関数の呼び出し順序は「unspecified order(未指定の順序)」とされていました。これは、コンパイラの実装やビルドシステムがファイルを処理する順序に依存する可能性があり、異なる環境やGoのバージョン間で挙動が異なるリスクをはらんでいました。例えば、ファイル名の辞書順に処理されることが慣例的に期待される場合がありましたが、それが仕様として保証されているわけではありませんでした。

このコミットによって、doc/go_spec.htmlの該当箇所が以下のように変更されました。

変更前: functions in unspecified order.

変更後: functions in the order they appear in the source, possibly in multiple files, as presented to the compiler.

この変更は、以下の重要な意味を持ちます。

  1. 予測可能性の向上: init()関数の実行順序がソースコード上の出現順に厳密に定義されたことで、開発者は初期化ロジックの挙動をより正確に予測できるようになりました。これにより、初期化時の競合状態やデッドロックのリスクを低減し、より堅牢なプログラムを記述することが可能になります。
  2. 一貫性の確保: 独立した変数の初期化順序が既にソースコード上の出現順に定義されていたため、init()関数の順序もこれに合わせることで、Go言語全体の初期化セマンティクスに一貫性がもたらされました。これにより、言語仕様の理解が容易になり、学習曲線が緩やかになります。
  3. ビルドシステムへの影響: 「as presented to the compiler(コンパイラに提示される順序)」という文言は、ビルドシステムがGoソースファイルをコンパイラに渡す際の順序が重要であることを示唆しています。しかし、Goのツールチェイン(go buildなど)は通常、この「ソースコード上の出現順」を尊重するように設計されており、開発者が意識的にファイルの順序を操作する必要はほとんどありません。この変更は、Goのツールチェインが内部的にどのようにファイルを処理し、init()関数の順序を決定するかについての指針を明確にするものです。
  4. 後方互換性: コミットメッセージにもあるように、「While technically a language change, no existing code could have relied on this, so this should not break anything.(技術的には言語の変更ですが、既存のコードがこれに依存しているはずがないため、何も壊れることはないでしょう。)」とされています。これは、以前の「未指定の順序」という状態では、特定の順序に依存するコードはそもそも移植性がないか、未定義の挙動に依存していたため、この変更によって既存のコードが壊れる可能性は極めて低いという判断です。

この変更は、Go言語が初期の段階から成熟した言語へと進化する過程で、より厳密で予測可能な挙動を追求する姿勢を示しています。

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

変更はGo言語の仕様書ファイル doc/go_spec.html の1箇所のみです。

--- a/doc/go_spec.html
+++ b/doc/go_spec.html
@@ -6008,7 +6008,8 @@ in a program.
 <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.
+functions in the order they appear in the source, possibly in multiple files,
+as presented to the compiler.
 If a package has imports, the imported packages are initialized
 before initializing the package itself. If multiple packages import
 a package, the imported package will be initialized only once.

コアとなるコードの解説

変更された行は、Go言語のパッケージ初期化に関するセクションにあります。

元の記述: functions in unspecified order. (関数は未指定の順序で。)

この記述は、init()関数が呼び出される順序がGo言語の仕様によって保証されていないことを意味していました。これは、コンパイラの実装やビルドシステムがファイルを処理する順序に依存する可能性があり、開発者が特定の順序に依存するコードを書くことを推奨しないものでした。

変更後の記述: functions in the order they appear in the source, possibly in multiple files, as presented to the compiler. (関数は、ソースコード上に出現する順序で、複数のファイルにまたがる場合でも、コンパイラに提示される順序で。)

この新しい記述は、init()関数の呼び出し順序を明確に定義しています。

  • in the order they appear in the source: これは、同じファイル内に複数のinit()関数がある場合、それらがソースコードの上から下へ記述された順序で実行されることを意味します。
  • possibly in multiple files: これは、1つのパッケージが複数のGoソースファイルで構成されている場合でも、このルールが適用されることを示しています。つまり、異なるファイルに分散しているinit()関数も、全体としてソースコード上に出現する順序で実行されます。
  • as presented to the compiler: この部分は、ビルドシステムがGoソースファイルをコンパイラに渡す際の順序が、最終的なinit()関数の実行順序に影響を与える可能性があることを示唆しています。しかし、Goの標準ツールチェイン(go buildなど)は、通常、この「ソースコード上の出現順」を尊重するようにファイルを処理するため、一般的な開発においては意識する必要はほとんどありません。この文言は、より低レベルなコンパイラの挙動や、カスタムビルドシステムを使用する場合の考慮事項を示しています。

この変更により、Go言語のinit()関数の実行順序は、より予測可能で厳密なものとなり、開発者は初期化ロジックをより安心して記述できるようになりました。

関連リンク

参考にした情報源リンク