[インデックス 10093] ファイルの概要
このコミットは、Go言語のコンパイラ (gc
) および関連ツール (gopack
, ld
) において、rune
型を導入するものです。rune
はGo言語におけるUnicodeコードポイントを表す型であり、文字列処理における多言語対応を強化するために不可欠な要素です。この変更により、Goコンパイラはrune
型を認識し、適切に処理できるようになり、文字列と[]rune
(runeのスライス)間の変換や、文字列のイテレーションがrune
ベースで行われるようになります。
コミット
- コミットハッシュ:
6ed3fa6553d84391157eae963eeee5f20b6dca74
- Author: Russ Cox rsc@golang.org
- Date: Tue Oct 25 22:19:39 2011 -0700
- コミットメッセージ:
gc: introduce rune R=ken, r CC=golang-dev https://golang.org/cl/5293046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6ed3fa6553d84391157eae963eeee5f20b6dca74
元コミット内容
gc: introduce rune
R=ken, r
CC=golang-dev
https://golang.org/cl/5293046
変更の背景
Go言語は設計当初からUnicodeとUTF-8を強く意識しており、文字列の扱いにおいて多バイト文字を正しく処理することが求められていました。従来のGoの文字列はバイトのシーケンスとして扱われ、ASCII文字以外の文字(特にUTF-8で複数バイトを占める文字)を扱う際に、文字単位での操作が困難でした。
このコミットは、Go言語が文字列を単なるバイト列としてではなく、Unicodeコードポイントのシーケンスとして扱えるようにするための基盤を構築します。rune
型の導入により、開発者は個々のUnicode文字(コードポイント)をより直感的に操作できるようになり、多言語対応のアプリケーション開発が容易になります。特に、文字列のイテレーションや、文字列と文字の配列([]rune
)間の変換において、rune
型が中心的な役割を果たすようになります。
また、このコミットでは、コンパイラの実験的機能 (GOEXPERIMENT
) の仕組みも導入されており、rune
型がint
(通常32ビット)として扱われるか、あるいは将来的にint32
として明示的に扱われるかを選択できるような柔軟性も持たせています。これは、Go言語の進化の過程で、型のセマンティクスを慎重に検討していた時期のコミットであることを示唆しています。
前提知識の解説
UnicodeとUTF-8
- Unicode: 世界中のあらゆる文字を統一的に扱うための文字コードの国際標準です。各文字には一意の「コードポイント」と呼ばれる番号が割り当てられています。例えば、
'A'
はU+0041、'あ'
はU+3042といったコードポイントを持ちます。 - UTF-8: Unicodeのコードポイントをバイト列にエンコード(符号化)するための可変長エンコーディング方式の一つです。ASCII文字は1バイトで表現され、それ以外の文字は2バイト以上で表現されます。これにより、ASCIIとの互換性を保ちつつ、効率的にUnicode文字を表現できます。Go言語の文字列は内部的にUTF-8でエンコードされたバイト列として扱われます。
Go言語のrune
型
Go言語において、rune
はint32
のエイリアス(別名)です。これは、Unicodeのコードポイントを表現するために使用されます。Goの文字列はUTF-8バイトのシーケンスですが、for range
ループで文字列をイテレートすると、各要素はrune
型(Unicodeコードポイント)として取得されます。これにより、多バイト文字も正しく1文字として扱えるようになります。
例:
s := "Hello, 世界"
for i, r := range s {
fmt.Printf("Index: %d, Rune: %c (U+%04X)\n", i, r, r)
}
このコードを実行すると、「世」や「界」のような多バイト文字も正しく1つのrune
として処理されることがわかります。
Goコンパイラ (gc
)
Go言語の公式コンパイラです。Goのソースコードを機械語に変換する役割を担います。このコミットでは、gc
がrune
型を認識し、型チェック、コード生成、および内部表現において適切に処理するように変更が加えられています。
gopack
とld
gopack
: Goのパッケージアーカイブを操作するためのツールです。コンパイルされたオブジェクトファイル(.a
ファイル)をまとめる役割を持ちます。このコミットでは、オブジェクトファイルのヘッダにコンパイラの実験的設定 (GOEXPERIMENT
) を含めるための変更が行われています。ld
: Goのリンカです。コンパイルされたオブジェクトファイルやライブラリを結合し、実行可能なバイナリを生成します。このコミットでは、リンカがオブジェクトファイルのヘッダをチェックする際に、GOEXPERIMENT
の情報も考慮するように変更されています。
GOEXPERIMENT
Go言語のコンパイラには、実験的な機能を有効にするためのGOEXPERIMENT
環境変数があります。この環境変数に特定のキーワードを設定することで、まだ安定版ではない新機能を試すことができます。このコミットでは、rune32
という実験が導入されており、rune
型が常にint32
として扱われるかどうかを制御できるようになっています。
技術的詳細
このコミットの主要な技術的変更点は以下の通りです。
-
rune
型の導入と定義:src/cmd/gc/go.h
にrunetype
という新しいType*
が追加され、rune
型を表すためのグローバル変数が定義されました。src/cmd/gc/lex.c
のlexinit1
関数内で、rune
というシンボルがGo言語の組み込み型として認識され、runetype
にマッピングされるようになりました。GOEXPERIMENT
のrune32
が有効な場合、runetype
はTINT32
(32ビット整数)として定義され、そうでない場合はTINT
(システムのデフォルト整数型、通常は32ビット)として定義されます。これにより、rune
がUnicodeコードポイントを表現するint32
のエイリアスであることが明確になります。
-
コンパイラの実験的機能の管理:
src/cmd/gc/lex.c
にexper
という構造体の配列が追加され、GOEXPERIMENT
環境変数で制御される実験的機能(この場合はrune32
)を管理する仕組みが導入されました。setexp
関数はGOEXPERIMENT
環境変数を解析し、有効な実験を内部フラグに設定します。expstring
関数は現在有効な実験の文字列表現を生成し、コンパイルされたオブジェクトファイルのヘッダに埋め込まれる情報の一部として使用されます。
-
オブジェクトファイルのヘッダ情報の拡張:
src/cmd/gc/lex.c
、src/cmd/gc/obj.c
、src/cmd/ld/lib.c
、src/cmd/gopack/ar.c
において、コンパイルされたオブジェクトファイルやアーカイブファイルのヘッダに、Goのバージョン、OS、アーキテクチャに加えて、GOEXPERIMENT
で有効になっている実験情報 (expstring()
) が含まれるようになりました。- これにより、リンカや
gopack
は、異なる実験設定でコンパイルされたオブジェクトファイルが混在していないかをチェックできるようになり、互換性の問題を早期に検出できます。
-
文字列と
[]rune
間の変換関数の更新:src/cmd/gc/builtin.c.boot
とsrc/cmd/gc/runtime.go
において、sliceinttostring
がslicerunetostring
に、stringtosliceint
がstringtoslicerune
に、stringiter2
の戻り値がint
からrune
に変更されました。これは、文字列と整数のスライス間の変換が、Unicodeコードポイントのスライス([]rune
)との変換に置き換えられたことを意味します。src/pkg/runtime/string.goc
では、これらのランタイム関数の実装がint
からrune
への変更に合わせて更新されています。
-
型チェックとコード生成の調整:
src/cmd/gc/export.c
、src/cmd/gc/reflect.c
、src/cmd/gc/subr.c
において、bytetype
と同様にrunetype
も特別な組み込み型として扱われるように変更されました。src/cmd/gc/range.c
では、for range
ループで文字列をイテレートする際の要素の型がint
からrune
に変更されました。src/cmd/gc/typecheck.c
では、文字列リテラルから配列を生成する際に、UTF-8エンコードされた[]int
ではなく[]rune
として扱われるように変更されました。src/cmd/gc/walk.c
では、OARRAYRUNESTR
([]rune
から文字列への変換)とOSTRARRAYRUNE
(文字列から[]rune
への変換)のコード生成が、新しいランタイム関数slicerunetostring
とstringtoslicerune
を呼び出すように更新されました。
これらの変更は、Go言語がUnicodeを第一級の市民として扱うための重要なステップであり、コンパイラ、リンカ、ランタイムの各層にわたる広範な修正を伴っています。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は多岐にわたりますが、特に重要なファイルと変更点を以下に示します。
-
src/cmd/gc/lex.c
:GOEXPERIMENT
の処理ロジック(exper
配列、addexp
、setexp
、expstring
関数)が追加されました。main
関数でsetexp()
が呼び出され、コンパイラの起動時に実験設定が読み込まれるようになりました。lexinit1
関数でrune
シンボルが組み込み型として定義され、runetype
にマッピングされるようになりました。rune32
実験の有無によってrunetype
がTINT32
またはTINT
に設定されます。
-
src/cmd/gc/go.h
:EXTERN Type* runetype;
が追加され、rune
型を表すグローバルな型ポインタが宣言されました。EXTERN int rune32;
が追加され、rune32
実験が有効かどうかを示すフラグが宣言されました。
-
src/cmd/gc/builtin.c.boot
およびsrc/cmd/gc/runtime.go
:- ランタイムの組み込み関数定義が
int
からrune
に更新されました。特に、sliceinttostring
がslicerunetostring
に、stringtosliceint
がstringtoslicerune
に、stringiter2
の戻り値がretv int
からretv rune
に変更されました。
- ランタイムの組み込み関数定義が
-
src/pkg/runtime/string.goc
:sliceinttostring
とstringtosliceint
のランタイム実装が、それぞれslicerunetostring
とstringtoslicerune
にリネームされ、int
の代わりにrune
(int32
)を扱うように変更されました。
-
src/cmd/gc/range.c
:- 文字列の
for range
ループにおける値の型がTINT
からrunetype
に変更されました。
- 文字列の
-
src/cmd/gopack/ar.c
およびsrc/cmd/ld/lib.c
:- オブジェクトファイルのヘッダのバージョンチェックロジックが拡張され、
GOEXPERIMENT
情報も比較対象に含まれるようになりました。gopack
ではmatchhdr
関数が導入され、より柔軟なヘッダ比較が可能になりました。
- オブジェクトファイルのヘッダのバージョンチェックロジックが拡張され、
コアとなるコードの解説
src/cmd/gc/lex.c
の変更
// Compiler experiments.
// These are controlled by the GCEXPERIMENT environment
// variable recorded when the compiler is built.
static struct {
char *name;
int *val;
} exper[] = {
{"rune32", &rune32},
};
static void
addexp(char *s)
{
int i;
for(i=0; i<nelem(exper); i++) {
if(strcmp(exper[i].name, s) == 0) {
*exper[i].val = 1;
return;
}
}
print("unknown experiment %s\\n", s);
exits("unknown experiment");
}
static void
setexp(void)
{
char *f[20];
int i, nf;
// The makefile #defines GOEXPERIMENT for us.
nf = getfields(GOEXPERIMENT, f, nelem(f), 1, ",");
for(i=0; i<nf; i++)
addexp(f[i]);
}
char*
expstring(void)
{
int i;
static char buf[512];
strcpy(buf, "X");
for(i=0; i<nelem(exper); i++)
if(*exper[i].val)
seprint(buf+strlen(buf), buf+sizeof buf, ",%s", exper[i].name);
if(strlen(buf) == 1)
strcpy(buf, "X,none");
buf[1] = ':';
return buf;
}
このセクションは、Goコンパイラが実験的な機能をどのように管理するかを示しています。exper
配列は、rune32
のような実験の名前と、その実験が有効になったときに設定されるフラグ(rune32
変数のアドレス)を関連付けます。setexp
関数は、コンパイラのビルド時にMakefileによって定義されるGOEXPERIMENT
環境変数を解析し、有効な実験に対応するフラグを設定します。expstring
関数は、現在有効な実験のリストを文字列としてフォーマットし、オブジェクトファイルのヘッダに埋め込むために使用されます。これにより、コンパイルされたコードがどの実験的機能に依存しているかを追跡できるようになります。
// rune alias
s = lookup("rune");
s->lexical = LNAME;
if(rune32)
runetype = typ(TINT32);
else
runetype = typ(TINT);
runetype->sym = s;
s1 = pkglookup("rune", builtinpkg);
s1->lexical = LNAME;
s1->def = typenod(runetype);
このコードは、rune
型がGo言語の組み込み型として認識されるようにする部分です。lookup("rune")
でrune
シンボルを取得し、lexical
プロパティをLNAME
(名前)に設定します。重要なのは、rune32
フラグ(GOEXPERIMENT=rune32
が設定されている場合)に基づいてrunetype
がTINT32
(32ビット整数)またはTINT
(デフォルトの整数型)のどちらになるかを決定している点です。これにより、rune
がUnicodeコードポイントを表現するint32
のエイリアスであることがコンパイラに伝えられます。
src/pkg/runtime/string.goc
の変更
func slicerunetostring(b Slice) (s String) {
// ... (implementation details) ...
}
func stringtoslicerune(s String) (b Slice) {
// ... (implementation details) ...
}
これらの関数は、Goランタイムにおける[]rune
とstring
間の変換を実装しています。以前はsliceinttostring
とstringtosliceint
という名前でしたが、rune
型の導入に伴い、よりセマンティックな名前に変更されました。これらの関数は、UTF-8エンコードされた文字列とUnicodeコードポイントのスライス間で効率的に変換を行うための低レベルな処理を担っています。例えば、stringtoslicerune
は文字列のバイト列をUTF-8デコードし、各Unicodeコードポイントをrune
としてスライスに格納します。
src/cmd/gc/range.c
の変更
case TSTRING:
t1 = types[TINT];
t2 = runetype; // Changed from types[TINT]
break;
この変更は、Goのfor range
ループで文字列をイテレートする際の挙動に直接影響します。文字列のfor range
ループは、インデックスと値のペアを返します。以前は値の型もint
(バイトオフセットや文字コード)として扱われる可能性がありましたが、この変更により、値の型が明示的にrunetype
(Unicodeコードポイント)として扱われるようになりました。これにより、多バイト文字も正しく1つのrune
として取得されることが保証されます。
関連リンク
- Go Code Review: https://golang.org/cl/5293046
参考にした情報源リンク
- Go言語における
rune
の概念と歴史: - Go言語のコンパイラとツールチェーンに関する一般的な情報:
- Goの公式ドキュメント
- Goのソースコードリポジトリ