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

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

このコミットは、Go言語のold/regexpold/template、およびtemplateパッケージにおいて、文字の表現をint型からrune型に変更するものです。これにより、Unicode文字の適切なハンドリングが強化され、より堅牢な文字列処理が可能になります。

コミット

commit cfa036ae3adffb56a2d93a074b97025a16519463
Author: Russ Cox <rsc@golang.org>
Date:   Tue Oct 25 22:22:42 2011 -0700

    old/regexp, old/template, template: use rune
    
    Nothing terribly interesting here.
    
    R=r, gri
    CC=golang-dev
    https://golang.org/cl/5308042

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

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

元コミット内容

old/regexp, old/template, template: use rune

このコミットは、old/regexpold/template、およびtemplateパッケージにおいて、文字の表現にrune型を使用するように変更します。

変更の背景

Go言語では、文字列はUTF-8でエンコードされたバイトのシーケンスとして扱われます。しかし、個々の文字(Unicodeコードポイント)を扱う際には、バイト列ではなくrune型を使用することが推奨されます。int型は通常、32ビットまたは64ビットの整数を表し、ASCII文字を扱う場合には問題ありませんが、Unicode文字(特にサロゲートペアや結合文字など)を正確に表現するには不十分な場合があります。

このコミットが行われた2011年10月は、Go言語がまだ比較的新しい時期であり、Unicodeの適切なサポートは継続的に改善されていました。regexp(正規表現)やtemplate(テンプレートエンジン)のような文字列処理が中心となるパッケージでは、文字の正確な解釈と操作が不可欠です。int型で文字を扱うと、マルチバイト文字が正しく処理されず、バグやセキュリティ上の問題を引き起こす可能性がありました。

この変更の背景には、Go言語の設計思想として、文字列と文字の扱いを明確にし、Unicodeを第一級市民として扱うという方針があったと考えられます。rune型への移行は、これらのパッケージがより国際化されたテキストデータを正確に処理できるようにするための重要なステップでした。

前提知識の解説

Go言語における文字列と文字

Go言語において、文字列は不変のバイトスライス([]byte)として内部的に表現され、UTF-8エンコーディングが使用されます。これは、GoがUnicodeをネイティブにサポートしていることを意味します。

  • string: Goのstring型は、UTF-8でエンコードされたバイトの読み取り専用シーケンスです。文字列の長さはバイト数で計算されます。
  • byte: byteuint8のエイリアスであり、1バイトのデータを表します。ASCII文字は1バイトで表現できますが、UTF-8エンコードされたUnicode文字は1バイトから4バイトの可変長になります。
  • rune: runeint32のエイリアスであり、単一のUnicodeコードポイントを表します。Goでは、for rangeループで文字列をイテレートすると、各要素はrune型として返され、そのruneが占めるバイト数も同時に取得できます。これにより、マルチバイト文字を正しく処理できます。

例えば、日本語の「あ」はUTF-8で3バイトですが、1つのruneとして扱われます。

s := "Hello, 世界"
fmt.Println(len(s)) // バイト数: 13
for i, r := range s {
    fmt.Printf("%d: %c (rune: %U)\n", i, r, r)
}
// 出力例:
// 0: H (rune: U+0048)
// 1: e (rune: U+0065)
// ...
// 7: 世 (rune: U+4E16)
// 10: 界 (rune: U+754C)

regexpパッケージとtemplateパッケージ

  • regexpパッケージ: Goの標準ライブラリに含まれる正規表現エンジンです。文字列パターンマッチングに使用されます。正規表現は、文字の集合や繰り返し、位置などを表現するため、文字の正確な解釈が非常に重要です。例えば、.(任意の文字)がマルチバイト文字を正しくマッチするかどうかは、内部での文字表現に依存します。
  • templateパッケージ: Goの標準ライブラリに含まれるテキストテンプレートエンジンです。HTMLやテキストの生成に使用されます。テンプレート内では、変数や関数、制御構造などが使用され、これらも文字列として解析・処理されます。

これらのパッケージがint型で文字を扱っていた場合、UTF-8のマルチバイト文字を正しく処理できず、予期せぬ動作やバグ(例: 文字化け、正規表現のマッチング失敗)が発生する可能性がありました。rune型への移行は、これらの問題を解決し、Unicode対応を強化するための根本的な変更です。

技術的詳細

このコミットの主要な変更点は、src/pkg/old/regexp/regexp.gosrc/pkg/old/template/parse.gosrc/pkg/template/exec_test.gosrc/pkg/template/funcs.gosrc/pkg/template/parse/lex.goの5つのファイルにわたって、文字を扱う変数の型をintからruneに変更していることです。

具体的には、以下のような変更が行われています。

  1. regexpパッケージ内の文字表現:

    • instr構造体のcharフィールドがintからruneに変更されました。instrは正規表現の命令を表す構造体で、charフィールドは特定の文字を保持するために使用されます。
    • charClass構造体のrangesフィールドが[]intから[]runeに変更されました。charClassは文字クラス(例: [a-z])を表し、rangesはその範囲を定義します。
    • charClassのメソッド(addRange, matches)の引数もintからruneに変更されました。
    • parser構造体のchフィールドがintからruneに変更されました。parserは正規表現の解析を担当し、chは現在処理中の文字を保持します。
    • parserのメソッド(c, nextc, special, ispunct, escape, checkBackslash)の引数や戻り値もintからruneに変更されました。
    • inputインターフェースのstepメソッドの戻り値がint, intからrune, intに変更されました。これは、入力ストリームから次の文字(rune)とそのバイト幅を読み取ることを示します。
    • Regexp構造体のLiteralPrefixメソッド内で使用されるスライスが[]intから[]runeに変更されました。
  2. templateパッケージ内の文字表現:

    • old/template/parse.goisExported関数で、utf8.DecodeRuneInStringの戻り値を受け取る変数がrune型になりました。
    • old/template/parse.gonewVariable関数で、クォートされた文字列をruneとして解釈する部分が[]int(v)[0]からutf8.DecodeRuneInString(v)に変更されました。これは、文字列から最初のruneを安全に抽出するための変更です。
    • template/exec_test.goのテスト関数内で定義されているstripSpace関数の引数と戻り値がintからruneに変更されました。
    • template/funcs.goJSEscape関連関数で、文字をチェックするjsIsSpecial関数の引数がintからruneに変更されました。また、utf8.DecodeRuneの戻り値を受け取る変数もrune型になりました。
    • template/parse/lex.golexer構造体のnextpeekメソッドの戻り値がintからruneに変更されました。lexerはテンプレートの字句解析を担当し、nextは次の文字を、peekは次の文字を覗き見します。
    • template/parse/lex.goisSpaceisAlphaNumeric関数の引数もintからruneに変更されました。

これらの変更は、Go言語の文字列処理におけるベストプラクティスに沿ったものであり、int型で文字を扱うことによって発生しうるUnicode関連の問題を根本的に解決することを目的としています。特に、正規表現やテンプレートのような文字単位での厳密な処理が求められる場面では、rune型を使用することで、あらゆるUnicode文字セットに対して正確かつ予測可能な動作が保証されます。

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

src/pkg/old/regexp/regexp.goinstr 構造体の変更が、このコミットの意図をよく表しています。

--- a/src/pkg/old/regexp/regexp.go
+++ b/src/pkg/old/regexp/regexp.go
@@ -119,7 +119,7 @@ type instr struct {
 	index int    // used only in debugging; could be eliminated
 	next  *instr // the instruction to execute after this one
 	// Special fields valid only for some items.
-	char   int        // iChar
+	char   rune       // iChar
 	braNum int        // iBra, iEbra
 	cclass *charClass // iCharClass
 	left   *instr     // iAlt, other branch

コアとなるコードの解説

上記の変更では、instr構造体のcharフィールドの型がintからruneに変更されています。

  • instr構造体: この構造体は、正規表現の内部表現における個々の「命令」を表します。例えば、「特定の文字にマッチする」という命令がある場合、その「特定の文字」を保持するのがcharフィールドです。
  • char int から char rune:
    • 変更前はchar intでした。これは、文字を単なる整数値として扱っていました。ASCII文字であれば、その文字のASCIIコードがそのまま整数値として格納されます。しかし、Unicode文字の場合、int型ではその文字のUnicodeコードポイントを直接表現できますが、その文字がUTF-8で何バイトを占めるか、あるいはそれが有効なUnicodeコードポイントであるかどうかのセマンティクスはint型だけでは保証されません。
    • 変更後はchar runeになりました。runeはGoにおいてUnicodeコードポイントを表すための専用の型(int32のエイリアス)です。これにより、charフィールドが保持する値が「単なる整数」ではなく「Unicode文字」であるという意図が明確になります。また、rune型を使用することで、Goの標準ライブラリ(unicodeパッケージやutf8パッケージ)が提供するrune操作関数をより自然に利用できるようになります。例えば、unicode.IsLetter(r rune)のような関数はrune型を引数に取るため、型変換なしで直接利用できます。

この変更は、正規表現エンジンが文字を扱う際の基盤となる部分であり、これにより正規表現がUnicode文字をより正確かつ堅牢に処理できるようになります。例えば、.(任意の文字)が絵文字やその他のマルチバイトUnicode文字を正しくマッチするようになるなど、国際化対応が強化されます。

関連リンク

参考にした情報源リンク