[インデックス 18971] ファイルの概要
このコミットは、Go言語の標準ライブラリ regexp/syntax
パッケージ内の prog.go
ファイルに対する変更です。regexp/syntax
パッケージは、正規表現の構文解析を行い、それを実行可能な命令列(プログラム)に変換する役割を担っています。prog.go
ファイルは、この命令列の定義や、命令に関するユーティリティ関数を含んでいます。具体的には、正規表現エンジンが内部的に使用する命令コード(InstOp
)の定数定義がこのファイルで行われています。
コミット
regexp/syntax: remove InstLast
This was added by the one-pass CL (post Go 1.2) so it can still be removed.
Removing because surely there will be new operations added later, and we can't change the constant value once we define it, so "last" is a bad concept to expose.
Nothing uses it.
LGTM=bradfitz R=golang-codereviews, bradfitz CC=golang-codereviews https://golang.org/cl/81160043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ef3c0e7e61ba1a670d69144b5ad8318166490ae8
元コミット内容
このコミットは、regexp/syntax
パッケージから InstLast
という定数を削除することを目的としています。この定数は、Go 1.2リリース後に導入された「one-pass CL」(おそらく正規表現エンジンの最適化に関する変更セット)によって追加されたものですが、まだ使用されていないため削除が可能であると判断されました。
削除の主な理由は、将来的に新しい命令が追加される可能性があり、一度定義された定数の値は変更できないため、「最後」を意味する InstLast
のような概念を公開することは不適切であるというものです。また、この定数がコードのどこからも使用されていないことも削除の理由として挙げられています。
変更の背景
この変更の背景には、Go言語における定数(const
)の性質と、ライブラリのAPI設計における将来的な拡張性の考慮があります。
Go言語において、const
キーワードで定義された定数はコンパイル時に値が決定され、実行時には変更できません。これは、プログラムの予測可能性を高め、最適化を容易にするという利点があります。しかし、一度公開された定数、特にその値が他のコードに依存する可能性がある場合、その値を変更することは後方互換性を破壊する変更となり、Go 1の互換性保証に反する可能性があります。
InstLast
という定数は、その名前が示す通り、InstOp
型の命令コードの「最後の値」を示すことを意図していたと考えられます。しかし、正規表現エンジンは進化するソフトウェアであり、将来的に新しい命令が追加される可能性は十分にあります。もし InstLast
が「現在の最後の命令コードの値」として定義されていた場合、新しい命令が追加されるたびに InstLast
の値を更新する必要が生じます。しかし、これは前述のGoの定数の性質(変更不可)と矛盾します。もし InstLast
が公開APIの一部として利用されていた場合、その値を変更することはユーザーコードに影響を与え、互換性の問題を引き起こすことになります。
このコミットの時点で InstLast
がどこからも使用されていないという事実は、この定数がまだ「実験的」または「未使用」の状態であったことを示唆しています。そのため、互換性の問題を発生させることなく、将来的な拡張性を阻害する可能性のあるこの定数を削除する絶好の機会と判断されました。これにより、regexp/syntax
パッケージは、将来の命令追加に対してより柔軟に対応できるようになります。
前提知識の解説
正規表現エンジンと命令セット
正規表現エンジンは、与えられた正規表現パターンを解析し、そのパターンにマッチする文字列を効率的に検索するためのソフトウェアです。多くの正規表現エンジンは、内部的に正規表現を一種の「プログラム」に変換して実行します。このプログラムは、特定の操作(文字のマッチ、繰り返し、選択など)を表す一連の「命令」(Instruction)で構成されます。
Go言語の regexp/syntax
パッケージは、この正規表現の構文解析と、その結果としての命令列(Prog
型)の生成を担当します。InstOp
型は、これらの命令の種類を識別するための列挙型(定数セット)です。例えば、InstRune
は特定の一文字にマッチする命令、InstRuneAny
は任意の一文字にマッチする命令などを表します。
Go言語における定数(const
)とiota
Go言語では、const
キーワードを使用して定数を定義します。定数はコンパイル時に評価され、その値は実行時に変更できません。
const (
A = 1
B = 2
)
また、Goには iota
という特殊な識別子があり、これは const
ブロック内で連続する整数値を自動的に生成するために使用されます。
const (
C = iota // C = 0
D // D = 1
E // E = 2
)
regexp/syntax/prog.go
の InstOp
定数も iota
を使用して定義されており、各命令コードに連続した整数値が割り当てられています。
const (
InstNop InstOp = iota
InstAlt
InstCat
InstRune
InstRune1
InstRuneAny
InstRuneAnyNotNL
// InstLast // 削除された行
)
この定義順序が重要で、InstLast
が存在していた場合、それは InstRuneAnyNotNL
の次の値(InstRuneAnyNotNL
が6であれば InstLast
は7)を持つことになります。
スライス(slice
)とlen()
関数
Go言語のスライスは、同じ型の要素の連続したシーケンスを表すデータ構造です。スライスは、基盤となる配列への参照、長さ(len
)、容量(cap
)を持ちます。
len()
関数は、スライス、配列、マップ、チャネル、文字列の長さを返す組み込み関数です。スライスの場合、len(s)
はスライス s
に含まれる要素の数を返します。
このコミットでは、InstLast
を使用して InstOp
の有効性をチェックしていた箇所が、len(instOpNames)
を使用するように変更されています。instOpNames
は InstOp
の各値に対応する文字列名を格納したスライスです。スライスの長さは、そのスライスに含まれる要素の数を正確に示します。これは、命令コードの最大値が、その名前を格納するスライスの要素数と一致するという前提に基づいています。
技術的詳細
このコミットの技術的な核心は、InstOp
の有効性チェックの方法を、将来の拡張に対してより堅牢なものに変更した点にあります。
元のコードでは、InstOp
の値が有効な命令コードであるかをチェックするために InstLast
という定数を使用していました。
func (i InstOp) String() string {
if i >= InstLast { // InstLast を使用したチェック
return ""
}
return instOpNames[i]
}
ここで InstLast
は、InstOp
定数ブロックの最後に定義されており、iota
の性質上、常にその時点での最後の命令コードの次の値を持っていました。例えば、InstRuneAnyNotNL
が 6
であれば、InstLast
は 7
となります。したがって、i >= InstLast
は「i
が定義されている命令コードの範囲外である」ことを意味していました。
しかし、このアプローチには問題があります。もし将来的に新しい命令コード(例: InstNewOp
)が InstRuneAnyNotNL
の後に追加された場合、InstLast
の値も変更される必要があります。しかし、Goの定数は変更できないため、これは不可能です。もし InstLast
がそのままの古い値で残された場合、新しい命令コードが追加されても i >= InstLast
のチェックは正しく機能せず、新しい命令コードが有効な範囲内であるにもかかわらず無効と判断される可能性があります。
この問題を解決するため、コミットでは InstLast
を削除し、代わりに instOpNames
スライスの長さを利用して有効性チェックを行うように変更しました。
func (i InstOp) String() string {
if uint(i) >= uint(len(instOpNames)) { // len(instOpNames) を使用したチェック
return ""
}
return instOpNames[i]
}
instOpNames
スライスは、InstOp
の各値に対応する文字列名を格納しています。新しい命令コードが追加されると、それに合わせて instOpNames
スライスも拡張され、その長さ(len(instOpNames)
)も自動的に更新されます。したがって、uint(i) >= uint(len(instOpNames))
というチェックは、常に現在の有効な命令コードの範囲を正確に反映するようになります。
uint(i)
と uint(len(instOpNames))
への型変換は、比較演算の安全性を確保するためです。InstOp
は int
の基底型を持つ可能性があり、len
関数は int
を返しますが、符号なし整数に変換することで、負の値の InstOp
が誤って有効と判断される可能性を防ぎ、また比較のセマンティクスを明確にしています。
この変更により、regexp/syntax
パッケージは、将来の命令コードの追加に対して、より柔軟かつ堅牢な設計となりました。
コアとなるコードの変更箇所
--- a/src/pkg/regexp/syntax/prog.go
+++ b/src/pkg/regexp/syntax/prog.go
@@ -36,7 +36,6 @@ const (
InstRune1
InstRuneAny
InstRuneAnyNotNL
- InstLast
)
var instOpNames = []string{
@@ -54,7 +53,7 @@ var instOpNames = []string{\
}\
func (i InstOp) String() string {\
-\tif i >= InstLast {\
+\tif uint(i) >= uint(len(instOpNames)) {\
\t\treturn ""\
\t}\
\treturn instOpNames[i]\
コアとなるコードの解説
このコミットによるコードの変更は2箇所です。
-
InstLast
定数の削除:- InstLast
const
ブロックからInstLast
という定数が削除されました。これにより、正規表現の命令コードの「最後」を示すという、将来の拡張性を阻害する可能性のある概念がコードベースから取り除かれました。 -
String()
メソッド内の有効性チェックの変更:- if i >= InstLast { + if uint(i) >= uint(len(instOpNames)) {
InstOp
型のString()
メソッドは、InstOp
の値に対応する文字列名を返すためのものです。このメソッド内で、与えられたInstOp
の値i
が有効な命令コードの範囲内にあるかをチェックするロジックが変更されました。- 変更前:
i >= InstLast
InstLast
はInstOp
定数ブロックの最後に定義されていたため、その時点での最後の命令コードの次の値を持っていました。この比較は、i
が定義済みの命令コードの範囲を超えているかをチェックしていました。 - 変更後:
uint(i) >= uint(len(instOpNames))
instOpNames
は、InstOp
の各値に対応する文字列名を格納するスライスです。新しい命令コードが追加されると、このスライスも拡張されます。したがって、len(instOpNames)
は常に現在の有効な命令コードの総数を正確に反映します。この変更により、InstOp
の有効性チェックは、将来新しい命令コードが追加されても自動的に対応できるようになり、より堅牢で保守しやすいコードになりました。uint()
への型変換は、比較の安全性を高めるためのものです。
- 変更前:
これらの変更により、regexp/syntax
パッケージは、将来の機能拡張に対してより柔軟に対応できる設計となりました。
関連リンク
- Go CL 81160043: https://golang.org/cl/81160043
- GitHub Commit: https://github.com/golang/go/commit/ef3c0e7e61ba1a670d69144b5ad8318166490ae8
参考にした情報源リンク
- Go言語の公式ドキュメント (regexp/syntax パッケージ): https://pkg.go.dev/regexp/syntax
- Go言語の定数に関するドキュメント: https://go.dev/ref/spec#Constants
- Go言語のスライスに関するドキュメント: https://go.dev/blog/go-slices-usage-and-internals
- Go言語の
len()
関数に関するドキュメント: https://go.dev/ref/spec#Length_and_capacity