[インデックス 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のソースコードリポジトリ