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

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

このコミットは、Go言語のcgoツールとgotestツールにおいて、生成されるコード内でエラーハンドリングに使用される型をos.Errorから標準のerrorインターフェースへ変更するものです。これは、Go言語のエラーハンドリングの進化と、os.Errorが非推奨になったことによる対応です。

コミット

  • コミットハッシュ: c8ad1a4dc4d0384d963df749cfc3c373e27d6a17
  • Author: Russ Cox rsc@golang.org
  • Date: Tue Nov 1 21:49:22 2011 -0400
  • コミットメッセージ:
    cgo, gotest: use error instead of os.Error in generated code
    
    R=golang-dev, iant
    CC=golang-dev
    https://golang.org/cl/5319057
    

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

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

元コミット内容

cgo, gotest: use error instead of os.Error in generated code

R=golang-dev, iant
CC=golang-dev
https://golang.org/cl/5319057

変更の背景

Go言語の初期のバージョンでは、エラーを表すためにos.Errorという具体的な型が使用されていました。しかし、Go言語のエラーハンドリングの設計思想は、より柔軟で汎用的なインターフェースベースのアプローチへと進化しました。その結果、os.Errorは非推奨となり、代わりに組み込みのerrorインターフェースを使用することが推奨されるようになりました。

このコミットは、cgo(C言語のコードをGoから呼び出すためのツール)とgotest(Goのテストフレームワーク)が生成するコードが、この新しいエラーハンドリングの慣習に準拠するように修正するためのものです。具体的には、cgoがC関数呼び出しのエラーをGoのコードに変換する際や、gotestがテスト実行時に使用する内部コードで、os.Errorではなくerrorインターフェースを使用するように変更されています。これにより、Go言語全体のエラーハンドリングの一貫性が保たれ、将来的な互換性も確保されます。

前提知識の解説

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

Go言語のエラーハンドリングは、他の多くの言語に見られるような例外処理(try-catchなど)とは異なり、関数の戻り値としてエラーを明示的に返すスタイルを採用しています。

  1. os.Error (旧来の方式): Go言語の非常に初期の段階では、osパッケージ内にErrorという型が存在し、エラーを表すために使われていました。これは、特定のパッケージに依存する具体的な型であり、Goのエラーハンドリングの哲学である「インターフェースによる抽象化」とは少し異なるアプローチでした。os.Errorは、エラーコードやエラーメッセージを保持する構造体のようなものでした。

  2. errorインターフェース (現在の標準): Go言語の現在の標準的なエラーハンドリングは、組み込みのerrorインターフェースに基づいています。このインターフェースは非常にシンプルで、Error() stringという単一のメソッドを定義しています。

    type error interface {
        Error() string
    }
    

    このインターフェースを実装する任意の型は、エラーとして扱うことができます。これにより、開発者は独自のカスタムエラー型を定義し、より詳細なエラー情報を提供したり、エラーの種類に基づいて異なる処理を行ったりすることが可能になります。関数がエラーを返す場合、通常は最後の戻り値としてerror型を返します。エラーがない場合はnilを返します。

    os.Errorからerrorインターフェースへの移行は、Go言語がより柔軟で、拡張性があり、かつ一貫性のあるエラーハンドリングメカニズムを提供するための重要なステップでした。

cgoとは

cgoは、GoプログラムからC言語のコードを呼び出すためのGoツールチェーンの一部です。GoとCの間の相互運用性を提供し、既存のCライブラリをGoプロジェクトで利用できるようにします。cgoは、Goのソースファイル内にCのコードを直接記述したり、Cのヘッダーファイルをインポートしたりすることで、GoとCの間のブリッジコードを自動生成します。

cgoがC関数をGoから呼び出すためのラッパーコードを生成する際、Cのerrno(システムコールが失敗した際に設定されるエラー番号)をGoのエラーとして扱う必要があります。このコミット以前は、このerrnoos.Errorとしてラップしていましたが、変更後はerrorインターフェースとしてラップするようになります。

gotestとは

gotestは、Go言語のテストフレームワークの実行を管理するツールです。Goのテストは、通常、_test.goというサフィックスを持つファイルに記述され、go testコマンドによって実行されます。gotestは、これらのテストファイルをコンパイルし、テストバイナリを実行し、結果を報告する役割を担います。

このコミットでは、gotestがテスト実行のために内部的に生成するコード(例えば、テストの正規表現マッチング関数など)において、エラー型をos.Errorからerrorインターフェースに修正しています。

技術的詳細

このコミットの技術的な核心は、Go言語のエラーハンドリングの標準化と、それに伴うツールチェーンの更新です。

  1. os.Errorからerrorインターフェースへの移行: Go言語の設計思想では、インターフェースは「振る舞い」を定義し、具体的な実装はそれに従います。errorインターフェースは、エラーが持つべき最小限の振る舞い(エラーメッセージを文字列として返すこと)を定義しています。これにより、どのようなエラーであっても、そのメッセージを取得できるという一貫性が保証されます。 os.Errorのような具体的な型に依存するのではなく、errorインターフェースに依存することで、cgogotestが生成するコードは、Goのエコシステム全体で利用されるカスタムエラー型や、将来的に導入される可能性のある新しいエラー処理メカニズムともシームレスに連携できるようになります。

  2. cgoにおけるC errnoの扱い: C言語の関数がエラーを返した場合、通常はグローバル変数errnoにエラーコードが設定されます。cgoは、このerrnoをGoのエラーとしてGoのコードに公開する機能を持っています。このコミットでは、_Cerrnoという内部関数がdst *os.Errorを受け取る代わりにdst *errorを受け取るように変更されています。これにより、CのerrnoがGoのerrorインターフェースとして適切に扱われるようになります。

  3. gotestにおける内部コードの修正: gotestは、テストの実行を効率化するために、テストバイナリに組み込まれる補助的なGoコードを生成します。例えば、テスト名の正規表現マッチングを行うmatchString関数などです。このmatchString関数も、エラーを返す際にos.Errorではなくerrorインターフェースを使用するように変更されています。これにより、テストフレームワーク自体がGoのエラーハンドリングのベストプラクティスに準拠します。

  4. import "os"の削除: os.Errorが使用されなくなったため、gotestの生成コードからimport "os"が不要になりました。これは、不要な依存関係を削除し、生成されるコードをよりクリーンにするための変更です。

これらの変更は、Go言語の進化に合わせて、その基盤となるツールチェーンも継続的に更新されていることを示しています。これにより、Go言語のコードベース全体の一貫性と保守性が向上します。

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

このコミットでは、以下の3つのファイルが変更されています。

  1. src/cmd/cgo/doc.go: cgoのドキュメントファイル。os.Errorに関する記述がerrorに修正されています。
  2. src/cmd/cgo/out.go: cgoがGoコードを生成する際のロジックが含まれるファイル。os.Error型を使用していた箇所がerrorインターフェースに置き換えられています。
  3. src/cmd/gotest/gotest.go: gotestツールの主要なロジックが含まれるファイル。テスト実行時に生成されるコード内でos.Errorを使用していた箇所がerrorインターフェースに置き換えられ、不要になったosパッケージのインポートが削除されています。

コアとなるコードの解説

src/cmd/cgo/doc.go

--- a/src/cmd/cgo/doc.go
+++ b/src/cmd/cgo/doc.go
@@ -59,7 +59,7 @@ struct_, union_, or enum_, as in C.struct_stat.
 
 Any C function that returns a value may be called in a multiple
 assignment context to retrieve both the return value and the
-C errno variable as an os.Error.  For example:
+C errno variable as an error.  For example:
 
 	n, err := C.atoi("abc")

この変更はドキュメントの修正です。cgoがCのerrno変数をGoのエラーとして返す際に、それがos.Errorではなくerrorインターフェースとして扱われることを明記しています。これは、ユーザーがcgoの挙動を正しく理解するための重要な情報です。

src/cmd/cgo/out.go

--- a/src/cmd/cgo/out.go
+++ b/src/cmd/cgo/out.go
@@ -48,7 +48,7 @@ func (p *Package) writeDefs() {
 	fmt.Fprintf(fgo2, "import \"os\"\\n\\n")
 	fmt.Fprintf(fgo2, "import _ \"runtime/cgo\"\\n\\n")
 	fmt.Fprintf(fgo2, "type _ unsafe.Pointer\\n\\n")
-	fmt.Fprintf(fgo2, "func _Cerrno(dst *os.Error, x int) { *dst = os.Errno(x) }\\n")
+	fmt.Fprintf(fgo2, "func _Cerrno(dst *error, x int) { *dst = os.Errno(x) }\\n")
 
 	for name, def := range typedef {
 		fmt.Fprintf(fgo2, "type %s ", name)
@@ -203,7 +203,7 @@ func (p *Package) structType(n *Name) (string, int64) {
 		off += pad
 	}
 	if n.AddError {
-		fmt.Fprint(&buf, "\t\tvoid *e[2]; /* os.Error */\\n")
+		fmt.Fprint(&buf, "\t\tvoid *e[2]; /* error */\\n")
 		off += 2 * p.PtrSize
 	}
 	if off == 0 {
@@ -217,9 +217,9 @@ func (p *Package) writeDefsFunc(fc, fgo2 *os.File, n *Name) {
 	name := n.Go
 	gtype := n.FuncType.Go
 	if n.AddError {
-		// Add "os.Error" to return type list.
+		// Add "error" to return type list.
 		// Type list is known to be 0 or 1 element - it's a C function.
-		err := &ast.Field{Type: ast.NewIdent("os.Error")}
+		err := &ast.Field{Type: ast.NewIdent("error")}
 		l := gtype.Results.List
 		if len(l) == 0 {
 			l = []*ast.Field{err}

このファイルでは、cgoが生成するGoコードのテンプレートが変更されています。

  • _Cerrno関数のシグネチャがdst *os.Errorからdst *errorに変更されています。この関数は、Cのerrno値をGoのerror型に変換して設定するために使用されます。
  • Cの構造体内でエラー情報を保持するためのコメントが/* os.Error */から/* error */に変更されています。これは、生成されるCコード内のコメントであり、Goのエラー型がerrorインターフェースであることを示唆しています。
  • writeDefsFunc関数内で、C関数がエラーを返す場合にGoの戻り値リストにos.Errorを追加していた箇所が、errorを追加するように変更されています。これは、cgoがC関数呼び出しのGoラッパーを生成する際に、エラーの戻り値型を正しく設定するための重要な変更です。

src/cmd/gotest/gotest.go

--- a/src/cmd/gotest/gotest.go
+++ b/src/cmd/gotest/gotest.go
@@ -401,7 +401,6 @@ func writeTestmainGo() {
 		fmt.Fprintf(b, "import target_test %q\\n", "./_xtest_")
 	}
 	fmt.Fprintf(b, "import %q\\n", "testing")
-	fmt.Fprintf(b, "import %q\\n", "os")
 	fmt.Fprintf(b, "import %q\\n", "regexp")
 	fmt.Fprintln(b) // for gofmt
 
@@ -454,7 +453,7 @@ var testBody = `
 var matchPat string
 var matchRe *regexp.Regexp
 
-func matchString(pat, str string) (result bool, err os.Error) {
+func matchString(pat, str string) (result bool, err error) {
 	if matchRe == nil || matchPat != pat {
 		matchPat = pat
 		matchRe, err = regexp.Compile(matchPat)

このファイルでは、gotestがテスト実行のために生成する_testmain.goのような内部コードのテンプレートが変更されています。

  • import "os"の行が削除されています。これは、os.Errorが使用されなくなったため、osパッケージへの依存が不要になったためです。
  • matchString関数のシグネチャがfunc matchString(...) (result bool, err os.Error)からfunc matchString(...) (result bool, err error)に変更されています。この関数は、テスト名の正規表現マッチングに使用される補助関数であり、エラーを返す際にerrorインターフェースを使用するように修正されています。

これらの変更により、cgogotestが生成するGoコードは、Go言語の最新のエラーハンドリングの慣習に準拠し、errorインターフェースを介してエラーを処理するようになります。

関連リンク

参考にした情報源リンク