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

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

このコミットは、Go言語のコンパイラ(gc)とランタイムにおける重要な変更を導入しています。主な目的は、Go言語の初期設計にあったexportキーワードの削除と、init関数の命名規則の変更です。これにより、Go言語の可視性ルールが簡素化され、init関数の内部的な管理が改善されました。

コミット

commit 0183baaf449338f54727814d079c0254c18226f9
Author: Russ Cox <rsc@golang.org>
Date:   Tue Jan 20 14:40:00 2009 -0800

    * delete export
    * rename init functions
    
    R=ken
    OCL=23122
    CL=23126

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

https://github.com/golang/go/commit/0183baaf449338f54727814d079c0254c18226f9

元コミット内容

    * delete export
    * rename init functions
    
    R=ken
    OCL=23122
    CL=23126

変更の背景

このコミットは、Go言語の初期開発段階における重要な設計変更を反映しています。

  1. exportキーワードの削除: Go言語の初期の設計では、C++やJavaのような言語に見られるpublicprivateに相当する可視性制御のためにexportキーワードが検討されていました。しかし、Go言語の設計者たちは、識別子(変数名、関数名など)の先頭文字が大文字であるか小文字であるかによって、その識別子がエクスポートされる(パッケージ外から参照可能になる)かどうかが決まるという、よりシンプルで慣用的なルールを採用することを決定しました。この変更により、冗長なキーワードが不要になり、コードの記述がより簡潔になりました。このコミットは、その設計変更をコンパイラの実装に反映したものです。

  2. init関数の命名規則の変更: Go言語のinit関数は、パッケージが初期化される際に自動的に実行される特殊な関数です。複数のファイルにinit関数が存在する場合や、パッケージ内で複数のinit関数が定義される場合、それらの関数がどのように管理され、呼び出されるかが問題となります。このコミット以前は、init関数はpkg.<file>_initのような形式で内部的に名前が変更されていました。しかし、この命名規則では、意図しない名前の衝突や、外部から誤って呼び出される可能性がありました。このコミットでは、init関数の内部名をpkg.init·filenameのような、よりユニークで、かつ外部から直接呼び出しにくい形式に変更することで、これらの問題を解決しようとしています。特に、·(中点)文字を使用することで、通常のGoの識別子としては無効な名前となり、外部からの参照を効果的に防いでいます。

前提知識の解説

Go言語の可視性ルール(エクスポートルール)

Go言語では、識別子(変数、関数、型、メソッドなど)の可視性は、その識別子の名前の先頭文字によって決定されます。

  • 大文字で始まる識別子: パッケージ外から参照可能です(エクスポートされます)。これは他の言語のpublicに相当します。
  • 小文字で始まる識別子: その識別子が定義されているパッケージ内でのみ参照可能です(エクスポートされません)。これは他の言語のprivateinternalに相当します。

このシンプルかつ強力なルールは、Go言語の設計哲学である「明示的であることよりも、慣習に従うこと」を体現しています。

Go言語のinit関数

Go言語のinit関数は、各パッケージに複数定義できる特殊な関数です。

  • 自動実行: init関数は、main関数が実行される前に、そのパッケージがインポートされた際に自動的に実行されます。
  • 引数なし、戻り値なし: init関数は引数を取らず、戻り値も持ちません。
  • 複数定義可能: 1つのパッケージ内に複数のinit関数を定義できます(異なるファイルに定義することも、同じファイルに複数定義することも可能です)。
  • 実行順序: 同じパッケージ内の複数のinit関数は、ファイル名の辞書順で実行され、各ファイル内のinit関数は定義された順に実行されます。インポートされたパッケージのinit関数は、そのパッケージの定数や変数の初期化が完了した後、かつそのパッケージを利用する側のパッケージのinit関数が実行される前に実行されます。

init関数は、プログラムの起動時に必要な初期設定(データベース接続、設定ファイルの読み込み、外部ライブラリの初期化など)を行うためによく使用されます。

技術的詳細

このコミットは、Goコンパイラ(gc)の内部実装に深く関わる変更を含んでいます。

  1. exportキーワードの削除:

    • src/cmd/gc/go.y: これはGoコンパイラのパーサーの定義ファイル(Yacc/Bison形式)です。exportキーワードに関連する文法規則(oexport, LEXPORT, export_list_rなど)が完全に削除されています。これにより、コンパイラはexportキーワードを構文として認識しなくなります。
    • src/cmd/gc/export.c: このファイルは、シンボルのエクスポートに関するロジックを扱っていました。exportキーワードの削除に伴い、exportsympackagesymといった関数が、識別子の先頭文字に基づく新しい可視性ルールに合わせて修正されています。特に、importsym関数の引数からexportフラグが削除され、シンボルのエクスポート状態はexportname関数(識別子の先頭文字をチェックする)によって決定されるようになりました。また、dumpexportconst, dumpexportvar, dumpexporttypeといった関数から、exportキーワードを出力するロジックが削除されています。
    • src/cmd/gc/go.h: dcladjというグローバル変数が削除されています。これは、宣言がエクスポートされるかパッケージスコープであるかを示すフラグとして使用されていましたが、exportキーワードの削除により不要になりました。
    • src/cmd/gc/sysimport.csrc/cmd/gc/unsafeimport.c: これらのファイルは、Goの組み込み関数やunsafeパッケージの定義をコンパイラに提供するものです。以前はexportキーワードがこれらの定義に含まれていましたが、このコミットで削除され、Goの新しい可視性ルールに統一されました。
  2. init関数の命名規則の変更:

    • src/cmd/gc/dcl.c: このファイルは宣言の処理を担当します。renameinit関数が変更され、init関数の内部名がinit_%s%sはファイル名)からinit·%sに変更されました。また、fninit関数内で生成される初期化コードのコメントが更新され、init_<file>_doneinitdone·<file>に、init_<file>_functionInit·<file>に変更されています。特に、mainパッケージのトップレベルのinit関数は、init_functionから単にinitという名前に変更され、ランタイムからの呼び出しを簡素化しています。
    • src/runtime/rt0_amd64.s: これはAMD64アーキテクチャ向けのランタイムの初期化コードです。mainstartルーチン内でmain·init_function(SB)を呼び出していた箇所がmain·init(SB)に変更されています。これは、mainパッケージのトップレベルのinit関数の内部名が変更されたことに対応しています。

これらの変更は、Go言語のコンパイラとランタイムの内部動作に深く関わるものであり、Go言語の設計思想である「シンプルさ」と「明瞭さ」を追求した結果と言えます。

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

  • src/cmd/gc/dcl.c:

    • renameinit関数: init関数の内部名をinit_%sからinit·%sに変更。
    • fninit関数: 初期化コード生成ロジック内の内部変数名(init_doneinit_function)をinitdone·%sInit·%sに変更。mainパッケージのトップレベルinit関数の名前をinit_functionからinitに変更。
    • fninit関数内のループ条件: インポートされたinit関数を検索する際の条件を、s->name[0] != 'i'strstr(s->name, "init_") == nilなどから、s->name[0] != 'I' || strncmp(s->name, "Init·", 6) != 0に変更。これは、新しい命名規則Init·filenameに対応するため。
  • src/cmd/gc/export.c:

    • autoexport関数: dcladjの使用を削除し、exportname関数に基づいて直接exportsymまたはpackagesymを呼び出すように変更。警告メッセージも削除。
    • dumpexportconst, dumpexportvar, dumpexporttype関数: exportキーワードの出力ロジックを削除。
    • importsym関数: export引数を削除し、exportname(ss->sym->name)の結果に基づいてs->exportを設定するように変更。
    • importconst, importvar, importtype関数: export引数を削除し、exportname関数によるチェックを直接行うように変更。
  • src/cmd/gc/go.h:

    • dcladjグローバル変数の削除。
    • importconst, importtype, importvar関数のプロトタイプからint型のexport引数を削除。
  • src/cmd/gc/go.y:

    • oexport型定義の削除。
    • LEXPORTおよびLPACKAGEに関連する文法規則(xdcl内のLEXPORTブロック、export_list_rexportoexportなど)を完全に削除。
    • hidden_importルールにおいて、oexportが削除されたことに伴い、importvar, importconst, importtype関数の呼び出しから対応する引数を削除。メソッドのインポートに関するコメントも修正。
  • src/cmd/gc/sysimport.c:

    • sysimport文字列内のすべてのexportキーワードを削除。
  • src/cmd/gc/unsafeimport.c:

    • unsafeimport文字列内のexportキーワードを削除。
  • src/runtime/rt0_amd64.s:

    • mainstartルーチン内のCALL main·init_function(SB)CALL main·init(SB)に変更。

コアとなるコードの解説

このコミットの核心は、Go言語の可視性メカニズムと初期化メカニズムの根本的な変更にあります。

exportキーワードの削除

以前のGo言語の設計では、C++やJavaのように明示的なexportキーワードを使用して、識別子をパッケージ外に公開するかどうかを制御していました。しかし、このコミットにより、そのアプローチは破棄され、Go言語独自の「識別子の先頭文字が大文字か小文字か」というルールに完全に移行しました。

  • go.yの変更: パーサーからexportキーワードの文法が削除されたことで、コンパイラはもはやexportという単語を特別な意味を持つキーワードとして認識しません。これにより、Goのソースコードからexportキーワードを削除しても、コンパイルエラーが発生しなくなります。
  • export.cの変更: export.cは、シンボルのエクスポートに関するコンパイラの内部ロジックを管理していました。このコミットでは、exportキーワードの有無に依存していたロジックが、exportname関数(識別子の先頭文字をチェックする)の呼び出しに置き換えられました。例えば、importsym関数は、インポートされるシンボルがエクスポートされるべきかどうかを、もはや引数で受け取るのではなく、シンボル名自体から判断するようになりました。これにより、コンパイラはGoの新しい可視性ルールに完全に準拠するようになりました。
  • sysimport.cunsafeimport.cの変更: これらのファイルは、Goの標準ライブラリや組み込み機能の定義をコンパイラに提供します。これらの定義からexportキーワードが削除されたことは、Go言語全体で新しい可視性ルールが徹底されたことを意味します。

この変更は、Go言語の設計哲学である「シンプルさ」と「明瞭さ」を追求した結果です。キーワードを減らし、慣習的な命名規則に依存することで、コードの読みやすさと記述のしやすさが向上しました。

init関数の命名規則の変更

Goのinit関数は、パッケージの初期化において重要な役割を果たします。このコミットは、これらの関数の内部的な命名規則を変更することで、その管理を改善しています。

  • dcl.cの変更:

    • renameinit関数は、ユーザーが定義したinit関数をコンパイラが内部的に扱うためのユニークな名前に変更する役割を担っています。以前はinit_ファイル名という形式でしたが、このコミットによりinit·ファイル名という形式に変更されました。ここで注目すべきは、·(中点)文字の使用です。この文字はGoの識別子としては無効であるため、この名前を持つ関数はGoのコードから直接呼び出すことができません。これにより、init関数が意図せず外部から呼び出されることを防ぎ、その特殊な性質をより明確にしています。
    • fninit関数は、パッケージの初期化処理をまとめた内部的な関数を生成します。この関数内で使用される内部的なフラグ変数(init_done)や、初期化処理全体をラップする関数(init_function)の名前も、同様にinitdone·ファイル名Init·ファイル名に変更されました。特に、mainパッケージのトップレベルの初期化関数は、ランタイムから直接呼び出されるため、init_functionから単にinitという簡潔な名前に変更されました。
    • fninit関数内のインポートされたinit関数を検索するロジックも、新しい命名規則に合わせて更新されました。これにより、コンパイラは他のパッケージからインポートされたinit関数を正しく識別し、初期化シーケンスに含めることができます。
  • rt0_amd64.sの変更:

    • rt0_amd64.sは、Goプログラムが起動する際に最初に実行されるアセンブリコードです。このコードは、mainパッケージの初期化関数を呼び出す責任があります。main·init_function(SB)からmain·init(SB)への変更は、mainパッケージのトップレベルの初期化関数の内部名が変更されたことに直接対応しています。これにより、ランタイムは新しい命名規則に従って初期化関数を正しく見つけ、実行できるようになります。

これらの変更は、init関数の内部的な管理をより堅牢にし、名前の衝突を防ぎ、その特殊な性質を強化することを目的としています。

関連リンク

  • Go言語の初期の設計に関する議論やメーリングリストのアーカイブは、Goプロジェクトの公式リポジトリや関連するメーリングリストで確認できる可能性があります。
  • Go言語のinit関数に関する公式ドキュメントやブログ記事は、Goの公式サイトで参照できます。

参考にした情報源リンク

  • Go言語の公式ドキュメント: https://go.dev/doc/
  • Go言語のブログ: https://go.dev/blog/
  • Go言語の初期の設計に関するメーリングリストアーカイブ (例: golang-nuts): https://groups.google.com/g/golang-nuts
  • Go言語のソースコードリポジトリ: https://github.com/golang/go
  • Go言語のinit関数に関する解説記事 (例: A Tour of Go, Effective Goなど)
  • Go言語の可視性ルールに関する解説記事 (例: A Tour of Go, Effective Goなど)
  • Go言語のコンパイラ(gc)の内部構造に関する資料 (より専門的な内容)