[インデックス 1486] ファイルの概要
このコミットは、Go言語の初期開発段階における、シンボルのエクスポート(公開)メカニズムに関する重要な変更を反映しています。具体的には、明示的な export
キーワードによる名前リストの指定を廃止し、パッケージローカルなスコープをデフォルトの動作とすることで、シンボルの可視性ルールを簡素化しています。また、識別子の大文字・小文字の区別によるエクスポートルールへの移行を促すための警告が導入されています。
コミット
commit 07d344e44260855eb8ee18f4265813a510c11f14
Author: Russ Cox <rsc@golang.org>
Date: Thu Jan 15 16:16:52 2009 -0800
remove export name-list statement.
make package local the default.
warn about name case not matching export keyword.
R=ken
OCL=22881
CL=22886
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/07d344e44260855eb8ee18f4265813a510c11f14
元コミット内容
remove export name-list statement.
make package local the default.
warn about name case not matching export keyword.
変更の背景
Go言語の設計思想の一つに「シンプルさ」と「明瞭さ」があります。初期のGo言語では、シンボルのエクスポート(他のパッケージからアクセス可能にする)には、C++やJavaのような明示的な export
キーワードや、特定の名前リストを記述する方法が検討されていました。しかし、これは冗長であり、コードの可読性を損なう可能性がありました。
このコミットが行われた2009年1月は、Go言語が一般に公開される前の初期開発段階にあたります。この時期に、Go言語の設計者たちは、よりGoらしい、簡潔で直感的なエクスポートメカニズムを模索していました。その結果として、識別子の先頭文字の大文字・小文字によってエクスポートを制御するという、現在Go言語で採用されている「大文字で始まる識別子はエクスポートされる」というルールへの移行が決定されました。
このコミットは、その移行プロセスの一環であり、従来の明示的な export
リストの廃止と、パッケージローカルがデフォルトであるという概念の導入、そして新しい大文字・小文字ルールへの適応を促すための過渡的な警告の追加を目的としています。これにより、言語の構文が簡素化され、開発者がシンボルの可視性をより直感的に理解できるようになります。
前提知識の解説
Go言語のシンボル可視性(エクスポート)ルール
Go言語では、パッケージ内の識別子(変数、関数、型など)の可視性(エクスポートされるか否か)は、その識別子の名前の先頭文字が大文字か小文字かによって決定されます。
- 大文字で始まる識別子: パッケージ外からアクセス可能(エクスポートされる)。
- 小文字で始まる識別子: パッケージ内でのみアクセス可能(パッケージローカル)。
このルールは、Go言語の設計哲学である「シンプルさ」と「明瞭さ」を体現しており、public
や private
といった明示的なキーワードを不要にしています。
PEXTERN
, PAUTO
, PPARAM
, PSTATIC
(Goコンパイラの内部表現)
これらはGoコンパイラ(特に gc
コマンド)の内部で使われる、シンボルの「クラス」または「宣言コンテキスト」を示す定数です。
PEXTERN
: 外部リンケージを持つシンボル(グローバル変数、エクスポートされた関数など)。PAUTO
: 自動変数(スタックに割り当てられるローカル変数)。PPARAM
: 関数パラメータ。PSTATIC
: (このコミット以前に存在した)静的リンケージを持つシンボル。C言語のstatic
と同様に、ファイルスコープ内で静的に割り当てられる変数などを指していた可能性があります。このコミットでは、このPSTATIC
の概念が廃止され、その役割は新しいデフォルトのパッケージローカルな可視性ルールに統合されたと考えられます。
go.y
(Yacc/Bison文法ファイル)
go.y
ファイルは、Go言語のパーサーを生成するためのYacc(またはBison)形式の文法定義ファイルです。Goコンパイラがソースコードを解析する際に、このファイルに定義された文法規則に従ってトークンを構文木に変換します。このファイルへの変更は、Go言語の構文そのものに変更が加えられたことを意味します。
dcladj
(Declaration Adjuster)
Goコンパイラの内部で、宣言されたシンボルに対して追加の処理(例えば、エクスポートマークを付けるなど)を行うための関数ポインタまたはフラグのようなものです。このコミットでは、dcladj
を直接操作する代わりに、autoexport
という新しい関数を通じて自動的にエクスポート処理を行うように変更されています。
技術的詳細
このコミットの技術的な核心は、Go言語のシンボル可視性管理モデルの根本的な変更にあります。
-
export
キーワードの簡素化と名前リストの廃止:src/cmd/gc/go.y
からexport_list_r
という文法規則が削除されています。これは、export (name1, name2)
のようにエクスポートするシンボルを明示的にリストアップする構文が廃止されたことを意味します。export
キーワード自体は残っていますが、その役割は「この宣言はエクスポートされる」という指示に限定され、どの名前をエクスポートするかは、もはやexport
ステートメント内で指定されなくなりました。
-
パッケージローカルのデフォルト化:
src/cmd/gc/go.y
でLPACKAGE
(おそらくpackage
キーワードに対応) の文法規則が変更され、warn("package is gone")
という警告を出すようになっています。これは、package
キーワードを使って明示的にシンボルをパッケージローカルに宣言する構文が廃止されたことを示唆しています。- これにより、シンボルはデフォルトでパッケージローカルとなり、エクスポートしたい場合は識別子を大文字で始めるか、あるいは過渡期的に
export
キーワードを使用する形に移行しました。
-
autoexport
メカニズムの導入:src/cmd/gc/export.c
にexportname(char *s)
関数が追加されました。この関数は、与えられたシンボル名がGoのエクスポートルール(先頭が大文字)に従っているかをチェックします。- 同じく
src/cmd/gc/export.c
にautoexport(Sym *s)
関数が追加されました。この関数は、シンボルs
がPEXTERN
コンテキストで宣言されており、かつexportname
のルールに従ってエクスポートされるべき名前である場合に、そのシンボルをエクスポートリストに追加します。 src/cmd/gc/dcl.c
では、dodclvar
,dodclconst
,dodcltype
といった宣言処理関数内で、従来のdcladj(n->sym)
の呼び出しがautoexport(n->sym)
に置き換えられています。これにより、シンボルが宣言されると同時に、その名前の規則に基づいて自動的にエクスポートされるかどうかが判断されるようになりました。
-
PSTATIC
の廃止:src/cmd/6g/gsubr.c
とsrc/cmd/gc/go.h
からPSTATIC
という宣言コンテキストが削除されています。これは、Go言語の設計において、C言語のような明示的な「静的」リンケージの概念が不要と判断され、その機能がパッケージローカルな可視性ルールに吸収されたことを示しています。
-
大文字・小文字ルールへの移行警告:
autoexport
関数内で、もしシンボル名がエクスポートされるべき(先頭が大文字)であるにもかかわらず、dcladj
がexportsym
でない(つまり、明示的なexport
キーワードが使われていない)場合にwarn("uppercase missing export")
という警告を出すようになっています。これは、開発者に対して、新しい大文字・小文字によるエクスポートルールへの移行を促すための、過渡的な措置です。最終的には、export
キーワード自体が不要になり、大文字・小文字のルールのみで可視性が決定されるようになります。
これらの変更は、Go言語のコンパイラと文法に深く関わるものであり、言語の設計思想がコードレベルでどのように具現化されていったかを示す重要な証拠です。
コアとなるコードの変更箇所
src/cmd/gc/dcl.c
--- a/src/cmd/gc/dcl.c
+++ b/src/cmd/gc/dcl.c
@@ -35,8 +35,7 @@ dodclvar(Node *n, Type *t)
t = typ(TFORW);
addvar(n, t, dclcontext);
- if(dcladj)
- dcladj(n->sym);
+ autoexport(n->sym);
}
void
@@ -49,8 +48,7 @@ dodclconst(Node *n, Node *e)
dodclconst(n, e);
addconst(n, e, dclcontext);
- if(dcladj)
- dcladj(n->sym);
+ autoexport(n->sym);
}
/*
@@ -79,8 +77,7 @@ dodcltype(Type *n)
found:
n->local = 1;
- if(dcladj)
- dcladj(n->sym);
+ autoexport(n->sym);
return n;
}
src/cmd/gc/export.c
--- a/src/cmd/gc/export.c
+++ b/src/cmd/gc/export.c
@@ -53,6 +53,32 @@ packagesym(Sym *s)
addexportsym(s);
}
+int
+exportname(char *s)
+{
+ Rune r;
+
+ if((uchar)s[0] < Runeself)
+ return 'A' <= s[0] && s[0] <= 'Z';
+ chartorune(&r, s);
+ return isupperrune(r);
+}
+
+void
+autoexport(Sym *s)
+{
+ if(s == S)
+ return;
+ if(dclcontext != PEXTERN)
+ return;
+ if(exportname(s->name)) {
+ if(dcladj != exportsym)
+ warn("uppercase missing export");
+ exportsym(s);
+ } else
+ packagesym(s);
+}
+
void
dumpprereq(Type *t)
{
@@ -330,6 +356,7 @@ importconst(int export, Node *ss, Type *t, Val *v)
Node *n;
Sym *s;
+ export = exportname(ss->sym->name);
if(export == 2 && !mypackage(ss))
return;
@@ -337,14 +364,18 @@ importconst(int export, Node *ss, Type *t, Val *v)
n->val = *v;
n->type = t;
- s = importsym(export, ss, LNAME);
+ s = importsym(export, ss, LACONST);
if(s->oconst != N) {
// TODO: check if already the same.
return;
}
+// fake out export vs upper checks until transition is over
+if(export == 1) dcladj = exportsym;
+
dodclconst(newname(s), n);
+dcladj = nil;
if(debug['e'])
print("import const %S\n", s);
}
src/cmd/gc/go.y
--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -188,20 +188,15 @@ xdcl:
{
$$ = N;
}
-|\tLEXPORT export_list_r
- {
- $$ = N;
- }
|\tLEXPORT { dcladj = exportsym; stksize = initstksize; } common_dcl
{
$$ = $3;
dcladj = 0;
initstksize = stksize;
}
-|\tLPACKAGE { dcladj = packagesym; stksize = initstksize; } common_dcl
+|\tLPACKAGE { warn("package is gone"); stksize = initstksize; } common_dcl
{
$$ = $3;
- dcladj = 0;
initstksize = stksize;
}
|\tLEXPORT '(' export_list_r ')'
@@ -214,10 +209,10 @@ xdcl:
exportsym($2->nname->sym);
$$ = N;
}
-|\tLPACKAGE xfndcl
+|\tLPACKAGE { warn("package is gone"); } xfndcl
{
- if($2 != N && $2->nname != N)
- packagesym($2->nname->sym);
+ if($3 != N && $3->nname != N)
+ packagesym($3->nname->sym);
$$ = N;
}
| ';'
コアとなるコードの解説
src/cmd/gc/dcl.c
の変更
dodclvar
, dodclconst
, dodcltype
はそれぞれ変数、定数、型の宣言を処理する関数です。これらの関数内で、以前は dcladj(n->sym)
が呼び出されていました。dcladj
は宣言されたシンボルに対して追加の調整を行うための汎用的なメカニズムでした。このコミットでは、この汎用的な呼び出しが autoexport(n->sym)
に置き換えられています。
これは、シンボルの宣言時に、そのシンボルが自動的にエクスポートされるべきかどうかを判断し、必要であればエクスポート処理を行うという、より特化したロジックに移行したことを意味します。これにより、宣言処理とエクスポート処理が密接に連携し、新しい大文字・小文字ルールに基づいた自動的な可視性制御が可能になります。
src/cmd/gc/export.c
の変更
-
exportname(char *s)
関数の追加: この関数は、Go言語の新しいエクスポートルールの中核をなすものです。if((uchar)s[0] < Runeself)
: シンボル名の最初のバイトがASCII文字であるかをチェックします。return 'A' <= s[0] && s[0] <= 'Z';
: ASCII文字の場合、それが大文字(A-Z)であるかをチェックします。chartorune(&r, s); return isupperrune(r);
: ASCII以外のUTF-8文字の場合、chartorune
でルーン(Unicodeコードポイント)に変換し、isupperrune
でそのルーンが大文字であるかをチェックします。 この関数は、Go言語の識別子のエクスポートルール(先頭文字が大文字であること)を正確に実装しています。
-
autoexport(Sym *s)
関数の追加: この関数は、シンボルs
を自動的にエクスポートするか、パッケージローカルにするかを決定します。if(s == S) return;
: 無効なシンボルはスキップします。if(dclcontext != PEXTERN) return;
: 宣言コンテキストがPEXTERN
(外部リンケージ) でない場合、エクスポートの対象外とします。これは、ローカル変数などが誤ってエクスポートされないようにするためです。if(exportname(s->name)) { ... }
:exportname
関数を使って、シンボル名がエクスポートされるべき名前(先頭が大文字)であるかをチェックします。if(dcladj != exportsym) warn("uppercase missing export");
: ここが重要な過渡期の警告です。もしシンボル名がエクスポートされるべき(大文字)なのに、現在のdcladj
がexportsym
でない場合(つまり、明示的なexport
キーワードが使われていない場合)、警告を発します。これは、開発者に新しい大文字・小文字ルールへの移行を促すためのものです。exportsym(s);
: シンボルをエクスポートリストに追加します。
else packagesym(s);
: シンボル名がエクスポートされるべきでない(先頭が小文字)場合、パッケージローカルとして扱います。
-
importconst
関数の変更:importconst
は定数をインポートする際に使われる関数です。export = exportname(ss->sym->name);
: インポートされる定数の名前がエクスポートルールに従っているかをexportname
でチェックし、その結果をexport
フラグに設定します。if(export == 1) dcladj = exportsym;
: これはコメントにもあるように「移行期間中のエクスポートと大文字・小文字のチェックをごまかす」ためのコードです。export
フラグが1(エクスポートされるべき)の場合に一時的にdcladj
をexportsym
に設定し、その後のdodclconst
呼び出しでエクスポート処理が正しく行われるようにしています。dcladj = nil;
: 処理後、dcladj
を元に戻しています。
src/cmd/gc/go.y
の変更
-
export_list_r
規則の削除:xdcl
(外部宣言) の規則から|\tLEXPORT export_list_r
が削除されています。これは、export (name1, name2)
のような、エクスポートする名前をリストアップする構文がGo言語から完全に削除されたことを意味します。 -
LPACKAGE
規則の変更:LPACKAGE
(おそらくpackage
キーワード) に関連する規則が変更され、warn("package is gone")
という警告を出すようになっています。また、dcladj = 0;
の行も削除されています。これは、明示的にpackage
キーワードを使ってシンボルをパッケージローカルに宣言する構文が廃止され、パッケージローカルがデフォルトの可視性になったことを示しています。
これらの変更は、Go言語のコンパイラがシンボルの可視性をどのように解釈し、処理するかという、言語の根幹に関わる部分を再定義しています。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/doc/
- Go言語の仕様: https://go.dev/ref/spec (特に "Exported identifiers" のセクション)
参考にした情報源リンク
- Go言語の歴史に関する議論やメーリングリストのアーカイブ (Go言語の初期開発に関する情報源)
- Go言語のソースコードリポジトリ (特に
src/cmd/gc
ディレクトリ内のファイル) - Yacc/Bisonのドキュメンテーション (文法ファイルの理解のため)
- Unicodeの文字プロパティに関する情報 (
isupperrune
の理解のため)