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

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

このコミットは、Go言語の標準ライブラリであるregexp/syntaxパッケージにおいて、新しく導入されたエラー定数ErrUnexpectedParenを、一時的に非公開(unexported)のerrUnexpectedParenに変更するものです。これは、Go 1.0.2のパッチリリースにおいてAPIの変更を避けるための措置であり、Go 1.1での再エクスポートが検討されています。

コミット

  • コミットハッシュ: 50452720ba8a582d24870bfe0b4d1a97e6652f4f
  • 作者: Russ Cox rsc@golang.org
  • コミット日時: 2012年6月8日 金曜日 13:05:01 -0400

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

https://github.com/golang/go/commit/50452720ba8a582d24870bfe0b4d1a97e6652f4f

元コミット内容

regexp/syntax: unexport ErrUnexpectedParen

This new error is the only API change in the current draft of
Go 1.0.2 CLs. I'd like to include the CL that introduced it,
because it replaces a mysterious 'internal error' with a
useful error message, but I don't want any API changes,
so unexport the error constant for now. It can be
re-exported for Go 1.1.

R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6294055

変更の背景

このコミットの背景には、Go言語のバージョン管理とAPI互換性に関する厳格なポリシーがあります。

  1. 新しいエラーの導入: regexp/syntaxパッケージにおいて、正規表現の構文解析中に発生する「予期せぬ閉じ括弧())」のエラーを、これまでの不明瞭な「内部エラー」ではなく、より具体的で分かりやすいErrUnexpectedParenというエラーメッセージとして提供する変更が提案されました。これはユーザーにとって非常に有用な改善です。

  2. Go 1.0.2のリリース方針: 当時、Go 1.0.2はバグ修正を主目的としたパッチリリースとして計画されていました。Go 1.x系のリリースでは、APIの互換性が非常に重視され、特にパッチリリースにおいては、既存のコードを壊す可能性のあるAPI変更は原則として行わないという方針がありました。

  3. API変更の回避: 新しいエラー定数ErrUnexpectedParenを公開(exported)すると、それはパッケージの公開APIの一部となり、Go 1.0.2におけるAPI変更とみなされてしまいます。開発チームは、この有用なエラーメッセージの改善をGo 1.0.2に含めたい一方で、API変更という原則を破りたくありませんでした。

  4. 一時的な非公開化: このジレンマを解決するため、ErrUnexpectedParenを一時的に非公開(unexported)のerrUnexpectedParenとすることで、API変更を回避しつつ、内部的には新しいエラーメッセージを使用できるようにしました。これにより、Go 1.0.2のAPI互換性ポリシーを維持しつつ、コードベースの改善を進めることができました。

  5. Go 1.1での再検討: コミットメッセージには「Go 1.1で再エクスポートできる」と明記されており、Go 1.1のようなメジャーバージョンアップでは、API変更がより許容される可能性があることを示唆しています。

前提知識の解説

Go言語のエラーハンドリング

Go言語では、エラーは組み込みのerrorインターフェースによって表現されます。関数がエラーを返す場合、通常は最後の戻り値としてerror型を返します。

func doSomething() (resultType, error) {
    // ... 処理 ...
    if someCondition {
        return zeroValue, fmt.Errorf("something went wrong: %w", err)
    }
    return actualResult, nil
}

エラーは文字列として表現されることが多いですが、より詳細な情報を持つカスタムエラー型を定義することも一般的です。このコミットで扱われているErrorCodeは、string型を基にしたカスタムエラー型であり、Error構造体の一部として使用されます。

Go言語のパッケージと識別子の可視性(エクスポート/アンエクスポート)

Go言語では、識別子(変数、関数、型、定数など)の名前の最初の文字が大文字か小文字かによって、その可視性(スコープ)が決定されます。

  • エクスポートされた識別子(Exported Identifiers): 識別子の最初の文字が大文字の場合、その識別子はパッケージの外部から参照可能です。これらはパッケージの公開APIの一部とみなされます。例: fmt.Println, regexp.Compile, regexp/syntax.ErrUnexpectedParen (変更前)。
  • 非エクスポートされた識別子(Unexported Identifiers): 識別子の最初の文字が小文字の場合、その識別子は定義されたパッケージ内でのみ参照可能です。パッケージの外部からは直接参照できません。これらはパッケージの内部実装の詳細とみなされ、API互換性の保証の対象外となることが多いです。例: fmt.print, regexp/syntax.errUnexpectedParen (変更後)。

このコミットは、ErrUnexpectedParenerrUnexpectedParenに変更することで、その可視性を「公開API」から「パッケージ内部実装」へと変更しています。

GoのリリースサイクルとAPI互換性

Go言語は、バージョン1.0以降、非常に厳格なAPI互換性ポリシーを維持しています。これは、Goプログラムの安定性と長期的なメンテナンス性を保証するための重要な原則です。

  • Go 1の互換性保証: Go 1.xのすべてのリリース(例: Go 1.0, Go 1.1, Go 1.2, ...)は、Go 1.0で導入された公開APIに対して後方互換性を持つことが保証されています。つまり、Go 1.0で書かれたプログラムは、Go 1.xの新しいバージョンでコンパイルし、実行できるはずです。
  • パッチリリース(1.x.y): Go 1.0.2のようなパッチリリースは、主にバグ修正、セキュリティアップデート、パフォーマンス改善を目的としています。これらのリリースでは、原則として新しいAPIの追加や既存APIの変更は行われません。
  • マイナーリリース(1.x): Go 1.1のようなマイナーリリースでは、新しい機能やAPIが追加されることがありますが、既存のAPIの動作を変更したり、削除したりすることは極力避けられます。
  • API変更の定義: 新しい公開された定数、変数、関数、型などを追加することは、APIの変更とみなされます。

このコミットは、Go 1.0.2のパッチリリースという性質上、API変更を避けるためにエラー定数を非公開化したという背景を強く反映しています。

regexp/syntaxパッケージ

regexp/syntaxパッケージは、Go言語の標準ライブラリであるregexpパッケージの一部であり、正規表現の構文解析(パース)と抽象構文木(AST)の構築を担当します。このパッケージは、正規表現のパターンを内部的な表現に変換し、その後のマッチング処理の基盤を提供します。通常、開発者がこのパッケージを直接使用することは稀で、regexpパッケージのCompile関数などを通じて間接的に利用されます。

技術的詳細

このコミットの技術的な核心は、Go言語の識別子の可視性ルールを利用して、APIの変更を回避した点にあります。

  1. 定数の可視性変更:

    • 変更前: const ErrUnexpectedParen ErrorCode = "unexpected )"
      • ErrUnexpectedParenは大文字で始まるため、regexp/syntaxパッケージの外部から参照可能な公開APIでした。
    • 変更後: const errUnexpectedParen ErrorCode = "unexpected )"
      • errUnexpectedParenは小文字で始まるため、regexp/syntaxパッケージ内でのみ参照可能な非公開の定数となりました。
  2. API互換性の維持:

    • この変更により、Go 1.0.2のユーザーは、regexp/syntax.ErrUnexpectedParenという新しい公開定数に依存するコードを書くことができなくなります。これにより、Go 1.0.2がAPI変更を含まないというGoの互換性保証が維持されます。
    • もしErrUnexpectedParenが公開されたままGo 1.0.2に含まれていれば、Go 1.0.1からGo 1.0.2へのアップグレードで、新しいシンボルが導入されたことになり、厳密にはAPI変更とみなされる可能性がありました。
  3. 内部的な改善の継続:

    • 定数を非公開にしたことで、regexp/syntaxパッケージの内部では、より具体的で有用なエラーメッセージ("unexpected )")を返すことができるようになりました。これにより、デバッグや問題の特定が容易になります。
    • 以前の「内部エラー」というメッセージは、問題の原因を特定するのに役立たず、開発者にとって不便でした。この改善は、ユーザーが直接エラー定数を参照できなくても、エラーメッセージ自体が改善されるという点で価値があります。
  4. 将来の再エクスポートの可能性:

    • コードには// TODO: Export for Go 1.1.というコメントが追加されています。これは、Go 1.1のような次のメジャーリリースでは、API変更が許容される可能性があるため、このエラー定数を再び公開APIとしてエクスポートする計画があることを示しています。これにより、将来的にユーザーがこの特定のエラーをプログラム的に処理できるようになる可能性があります。

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

src/pkg/regexp/syntax/parse.go ファイルにおける変更は以下の通りです。

--- a/src/pkg/regexp/syntax/parse.go
+++ b/src/pkg/regexp/syntax/parse.go
@@ -46,9 +46,11 @@ const (
 	ErrMissingParen          ErrorCode = "missing closing )"
 	ErrMissingRepeatArgument ErrorCode = "missing argument to repetition operator"
 	ErrTrailingBackslash     ErrorCode = "trailing backslash at end of expression"
-	ErrUnexpectedParen       ErrorCode = "unexpected )"
+	ErrUnexpectedParen       ErrorCode = "unexpected )" // 削除行
 )
 
+// TODO: Export for Go 1.1.
+const errUnexpectedParen ErrorCode = "unexpected )" // 追加行
+
 func (e ErrorCode) String() string {
 	return string(e)
 }
@@ -1169,13 +1171,13 @@ func (p *parser) parseRightParen() error {
 
 	n := len(p.stack)
 	if n < 2 {
-		return &Error{ErrUnexpectedParen, p.wholeRegexp} // 変更前
+		return &Error{errUnexpectedParen, p.wholeRegexp} // 変更後
 	}
 	re1 := p.stack[n-1]
 	re2 := p.stack[n-2]
 	p.stack = p.stack[:n-2]
 	if re2.Op != opLeftParen {
-		return &Error{ErrUnexpectedParen, p.wholeRegexp} // 変更前
+		return &Error{errUnexpectedParen, p.wholeRegexp} // 変更後
 	}
 	// Restore flags at time of paren.
 	p.flags = re2.Flags

コアとなるコードの解説

  1. 定数宣言の変更:

    • 元のコードでは、const ErrUnexpectedParen ErrorCode = "unexpected )"として、大文字で始まる公開定数として宣言されていました。
    • この行が削除され、代わりにconst errUnexpectedParen ErrorCode = "unexpected )"という小文字で始まる非公開定数が追加されました。
    • この変更により、ErrUnexpectedParenはパッケージ外部からアクセスできなくなり、APIの変更が回避されました。
    • // TODO: Export for Go 1.1.というコメントは、将来的にこの定数を再び公開する意図を示しています。
  2. parseRightParen関数内の参照の更新:

    • parseRightParen関数は、正規表現の構文解析中に閉じ括弧)を処理するロジックを含んでいます。
    • この関数内で、予期せぬ閉じ括弧が検出された場合にエラーを返す箇所が2箇所あります。
    • 変更前は、これらの箇所で&Error{ErrUnexpectedParen, p.wholeRegexp}のように公開定数ErrUnexpectedParenを参照していました。
    • 変更後は、&Error{errUnexpectedParen, p.wholeRegexp}のように、新しく定義された非公開定数errUnexpectedParenを参照するように修正されています。
    • これにより、エラーの型自体は*Errorのままであり、エラーメッセージも"unexpected )"と変わらないため、内部的な動作には影響を与えずに、APIの公開状態のみが変更されました。

このコミットは、Go言語の厳格なAPI互換性ポリシーと、内部的なコード品質の改善を両立させるための、非常にGoらしいアプローチを示しています。

関連リンク

参考にした情報源リンク