[インデックス 10196] ファイルの概要
このコミットは、Go言語の標準ライブラリ全体で、エラー型 os.Error
を組み込みの error
インターフェースにリネームする変更を反映したものです。これは、Go 1のリリースに向けた重要な変更の一環であり、エラーハンドリングの統一と簡素化を目的としています。ドキュメント、ツール、および様々なパッケージ内のコードがこの新しいエラーモデルに適合するように更新されています。
コミット
- コミットハッシュ:
492098eb759bba2ff5c86b0a868158afe32e91f8
- Author: Russ Cox rsc@golang.org
- Date: Tue Nov 1 22:58:09 2011 -0400
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/492098eb759bba2ff5c86b0a868158afe32e91f8
元コミット内容
all: rename os.Error to error in various non-code contexts
R=adg
CC=golang-dev
https://golang.org/cl/5328062
変更の背景
Go言語の初期バージョンでは、エラーを表すために os.Error
という具体的な型が使用されていました。しかし、Go 1のリリースに向けて、エラーハンドリングのメカニズムをより柔軟で統一されたものにする必要性が認識されました。
この変更の主な背景は以下の通りです。
- 統一されたエラーインターフェースの導入:
os.Error
はos
パッケージに属する具体的な型でしたが、エラーは言語の基本的な概念であり、特定のパッケージに限定されるべきではありません。そこで、Go言語の設計者は、error
という名前の組み込みインターフェースを導入することを決定しました。これにより、どのような型でもError() string
メソッドを実装していればerror
インターフェースを満たすことができ、より多様なエラー表現が可能になります。 - エラーハンドリングの簡素化と一貫性:
error
インターフェースの導入により、関数は具体的なエラー型ではなく、汎用的なerror
インターフェースを返すことができるようになりました。これにより、呼び出し側は特定のエラー型に依存することなく、一貫した方法でエラーを処理できるようになります。 - 言語の成熟: Go 1はGo言語の最初の安定版リリースであり、言語の設計と標準ライブラリのAPIを最終決定する重要な時期でした。この
os.Error
からerror
への変更は、言語の成熟度を高め、将来の拡張性を確保するための重要なステップでした。
このコミットは、この大規模な変更の一環として、コード以外のコンテキスト(ドキュメント、コメント、テストの期待値など)における os.Error
の参照を error
に更新するものです。
前提知識の解説
os.Error
(Go 1以前)
Go 1以前のGo言語では、エラーは os
パッケージで定義された os.Error
という具体的な型によって表現されていました。これは、エラーメッセージを保持するためのシンプルな構造体でした。
// Go 1以前の概念的なos.Errorの定義
package os
type Error interface {
String() string
}
type ErrorString string
func (e ErrorString) String() string {
return string(e)
}
func NewError(s string) Error {
return ErrorString(s)
}
関数がエラーを返す場合、その戻り値の型は os.Error
となっていました。
error
インターフェース (Go 1以降)
Go 1以降、os.Error
は廃止され、Go言語に組み込みの error
インターフェースが導入されました。このインターフェースは非常にシンプルで、Error() string
という単一のメソッドのみを定義しています。
// Go言語の組み込みerrorインターフェース
type error interface {
Error() string
}
この設計の重要な点は、任意の型が Error() string
メソッドを実装していれば、暗黙的に error
インターフェースを満たすという点です。これにより、開発者は独自のエラー型を定義し、それを error
として返すことができるようになり、エラーに付加情報を持たせたり、特定のエラーを型アサーションで識別したりすることが可能になりました。
errors
パッケージと errors.New
os.Error
の廃止に伴い、新しい errors
パッケージが導入されました。このパッケージには、シンプルなエラーを作成するための errors.New
関数が含まれています。
package errors
func New(text string) error {
return &errorString{text}
}
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
errors.New("some error message")
は、指定された文字列を返す Error()
メソッドを持つ error
型の値を返します。
go fix
ツール
Go言語には、古いAPIの使用を新しいAPIに自動的に変換する go fix
というツールがあります。os.Error
から error
への移行のような大規模な変更では、go fix
が多くのコードを自動的に更新するのに役立ちました。しかし、コメントやドキュメント内の参照など、コードのセマンティクスに直接影響しない箇所は手動での修正が必要となる場合がありました。
技術的詳細
このコミットは、Go言語のエラーハンドリングモデルの根本的な変更を反映しています。
- インターフェースとしてのエラー: 最も重要な変更は、エラーが具体的な型 (
os.Error
) からインターフェース (error
) になったことです。これにより、Goのエラーハンドリングはより柔軟で、Goのインターフェースの強力な特性を活かせるようになりました。開発者は、エラーにコンテキスト情報(例えば、エラーコード、スタックトレース、追加の詳細データ)を含めるために、カスタムエラー型を定義できるようになりました。 Error() string
メソッド:error
インターフェースはError() string
メソッドを要求します。これは、エラーを人間が読める文字列形式で表現するための標準的な方法を提供します。これにより、エラーログの出力やユーザーへのエラーメッセージ表示が一貫して行えます。nil
の意味: Goでは、nil
はエラーがないことを示します。これはos.Error
時代から変わっていませんが、error
インターフェースになったことで、カスタムエラー型がnil
ポインタとして返される場合でも、それがnil
と比較されるとnil
と評価されるというGoのインターフェースの特性が重要になります。os.ErrorString
からerrors.New
への移行:os.ErrorString
はos.Error
を実装する具体的な文字列型でした。このコミットでは、rpc
パッケージのドキュメントでos.ErrorString
がerrors.New
によって作成されたかのようにクライアントに見える、という説明に変わっています。これは、エラーの生成方法がerrors
パッケージに集約されたことを示しています。- ドキュメントとコメントの更新: このコミットの大部分は、コードの動作そのものを変更するのではなく、ドキュメント、コメント、テストの期待される出力など、コード以外の部分で
os.Error
という文字列をerror
に置き換えることに焦点を当てています。これは、新しいエラーモデルへの移行を完全に反映し、開発者が古い概念に混乱しないようにするための重要な作業です。例えば、doc/codelab/wiki/index.html
やdoc/debugging_with_gdb.html
のようなドキュメントファイルが更新されています。 govet
の更新:src/cmd/govet/govet.go
の変更は、govet
ツールがGoのコードを静的に分析する際に、新しいerror
インターフェースのシグネチャを正しく認識するように更新されたことを示しています。特に、標準的なインターフェースメソッド(GobDecode
,MarshalJSON
,ReadByte
など)の戻り値の型がos.Error
からerror
に変更されています。これにより、govet
はGo 1のエラーハンドリング規約に準拠しているかをチェックできるようになります。
コアとなるコードの変更箇所
このコミットは広範囲にわたる変更を含んでいますが、特に重要な変更箇所をいくつか抜粋して解説します。
1. ドキュメントの更新 (doc/codelab/wiki/index.html
)
--- a/doc/codelab/wiki/index.html
+++ b/doc/codelab/wiki/index.html
@@ -107,7 +107,7 @@ func (p *Page) save() error {
<p>
This method's signature reads: "This is a method named <code>save</code> that
takes as its receiver <code>p</code>, a pointer to <code>Page</code> . It takes
--no parameters, and returns a value of type <code>os.Error</code>."
+-no parameters, and returns a value of type <code>error</code>."
</p>
<p>
@@ -116,7 +116,7 @@ file. For simplicity, we will use the <code>Title</code> as the file name.
</p>
<p>
--The <code>save</code> method returns an <code>os.Error</code> value because
-+The <code>save</code> method returns an <code>error</code> value because
that is the return type of <code>WriteFile</code> (a standard library function
that writes a byte slice to a file). The <code>save</code> method returns the
error value, to let the application handle it should anything go wrong while
@@ -152,7 +152,7 @@ The function <code>loadPage</code> constructs the file name from
<p>
Functions can return multiple values. The standard library function
--<code>io.ReadFile</code> returns <code>[]byte</code> and <code>os.Error</code>.
-+<code>io.ReadFile</code> returns <code>[]byte</code> and <code>error</code>.
In <code>loadPage</code>, error isn't being handled yet; the "blank identifier"
represented by the underscore (<code>_</code>) symbol is used to throw away the
error return value (in essence, assigning the value to nothing).
@@ -161,7 +161,7 @@ error return value (in essence, assigning the value to nothing).
<p>
But what happens if <code>ReadFile</code> encounters an error? For example,
the file might not exist. We should not ignore such errors. Let's modify the
--function to return <code>*Page</code> and <code>os.Error</code>.
-+function to return <code>*Page</code> and <code>error</code>.
</p>
2. govet
ツールのメソッドシグネチャ定義の更新 (src/cmd/govet/govet.go
)
--- a/src/cmd/govet/govet.go
+++ b/src/cmd/govet/govet.go
@@ -232,23 +232,23 @@ type MethodSig struct {
// we let it go. But if it does have a fmt.ScanState, then the
// rest has to match.\nvar canonicalMethods = map[string]MethodSig{
- // "Flush": {{}, {"os.Error"}}, // http.Flusher and jpeg.writer conflict
- "Format": {[]string{"=fmt.State", "rune"}, []string{}}, // fmt.Formatter
- "GobDecode": {[]string{"[]byte"}, []string{"os.Error"}}, // gob.GobDecoder
- "GobEncode": {[]string{}, []string{"[]byte", "os.Error"}}, // gob.GobEncoder
- "MarshalJSON": {[]string{}, []string{"[]byte", "os.Error"}}, // json.Marshaler
- "MarshalXML": {[]string{}, []string{"[]byte", "os.Error"}}, // xml.Marshaler
- "Peek": {[]string{"=int"}, []string{"[]byte", "os.Error"}}, // image.reader (matching bufio.Reader)
- "ReadByte": {[]string{}, []string{"byte", "os.Error"}}, // io.ByteReader
- "ReadFrom": {[]string{"=io.Reader"}, []string{"int64", "os.Error"}}, // io.ReaderFrom
- "ReadRune": {[]string{}, []string{"rune", "int", "os.Error"}}, // io.RuneReader
- "Scan": {[]string{"=fmt.ScanState", "rune"}, []string{"os.Error"}}, // fmt.Scanner
- "Seek": {[]string{"=int64", "int"}, []string{"int64", "os.Error"}}, // io.Seeker
- "UnmarshalJSON": {[]string{"[]byte"}, []string{"os.Error"}}, // json.Unmarshaler
- "UnreadByte": {[]string{}, []string{"os.Error"}},
- "UnreadRune": {[]string{}, []string{"os.Error"}},
- "WriteByte": {[]string{"byte"}, []string{"os.Error"}}, // jpeg.writer (matching bufio.Writer)
- "WriteTo": {[]string{"=io.Writer"}, []string{"int64", "os.Error"}}, // io.WriterTo
+ // "Flush": {{}, {"error"}}, // http.Flusher and jpeg.writer conflict
+ "Format": {[]string{"=fmt.State", "rune"}, []string{}}, // fmt.Formatter
+ "GobDecode": {[]string{"[]byte"}, []string{"error"}}, // gob.GobDecoder
+ "GobEncode": {[]string{}, []string{"[]byte", "error"}}, // gob.GobEncoder
+ "MarshalJSON": {[]string{}, []string{"[]byte", "error"}}, // json.Marshaler
+ "MarshalXML": {[]string{}, []string{"[]byte", "error"}}, // xml.Marshaler
+ "Peek": {[]string{"=int"}, []string{"[]byte", "error"}}, // image.reader (matching bufio.Reader)
+ "ReadByte": {[]string{}, []string{"byte", "error"}}, // io.ByteReader
+ "ReadFrom": {[]string{"=io.Reader"}, []string{"int64", "error"}}, // io.ReaderFrom
+ "ReadRune": {[]string{}, []string{"rune", "int", "error"}}, // io.RuneReader
+ "Scan": {[]string{"=fmt.ScanState", "rune"}, []string{"error"}}, // fmt.Scanner
+ "Seek": {[]string{"=int64", "int"}, []string{"int64", "error"}}, // io.Seeker
+ "UnmarshalJSON": {[]string{"[]byte"}, []string{"error"}}, // json.Unmarshaler
+ "UnreadByte": {[]string{}, []string{"error"}},
+ "UnreadRune": {[]string{}, []string{"error"}},
+ "WriteByte": {[]string{"byte"}, []string{"error"}}, // jpeg.writer (matching bufio.Writer)
+ "WriteTo": {[]string{"=io.Writer"}, []string{"int64", "error"}}, // io.WriterTo
}
3. rpc
パッケージのドキュメントと型情報の更新 (src/pkg/rpc/server.go
)
--- a/src/pkg/rpc/server.go
+++ b/src/pkg/rpc/server.go
@@ -18,12 +18,12 @@
registering the service).
- the method has two arguments, both exported or local types.
- the method's second argument is a pointer.
-- - the method has return type os.Error.
+- - the method has return type error.
The method's first argument represents the arguments provided by the caller; the
second argument represents the result parameters to be returned to the caller.
The method's return value, if non-nil, is passed back as a string that the client
-- sees as an os.ErrorString.
+- sees as if created by errors.New.
The server may handle requests on a single connection by calling ServeConn. More
typically it will create a network listener and call Accept or, for an HTTP
@@ -55,14 +55,14 @@
type Arith int
- func (t *Arith) Multiply(args *Args, reply *int) os.Error {
+ func (t *Arith) Multiply(args *Args, reply *int) error {
*reply = args.A * args.B
return nil
}
- func (t *Arith) Divide(args *Args, quo *Quotient) os.Error {
+ func (t *Arith) Divide(args *Args, quo *Quotient) error {
if args.B == 0 {
- return os.ErrorString("divide by zero")
+ return errors.New("divide by zero")
}
quo.Quo = args.A / args.B
quo.Rem = args.A % args.B
@@ -133,10 +133,9 @@ const (
DefaultDebugPath = "/debug/rpc"
)
-// Precompute the reflect type for os.Error. Can't use os.Error directly
+// Precompute the reflect type for error. Can't use error directly
// because Typeof takes an empty interface value. This is annoying.
-var unusedError *error
-var typeOfOsError = reflect.TypeOf(unusedError).Elem()
+var typeOfError = reflect.TypeOf((*error)(nil)).Elem()
type methodType struct {
sync.Mutex // protects counters
@@ -210,13 +209,13 @@ func isExportedOrBuiltinType(t reflect.Type) bool {
// receiver value that satisfy the following conditions:
// - exported method
// - two arguments, both pointers to exported structs
-// - one return value, of type os.Error
+// - one return value, of type error
// It returns an error if the receiver is not an exported type or has no
// suitable methods.
// The client accesses each method using a string of the form "Type.Method",
@@ -281,13 +280,13 @@ func (server *Server) register(rcvr interface{}, name string, useName bool) erro
log.Println("method", mname, "reply type not exported or local:", replyType)
continue
}
- // Method needs one out: os.Error.
+ // Method needs one out: error.
if mtype.NumOut() != 1 {
log.Println("method", mname, "has wrong number of outs:", mtype.NumOut())
continue
}
- if returnType := mtype.Out(0); returnType != typeOfOsError {
- log.Println("method", mname, "returns", returnType.String(), "not os.Error")
+ if returnType := mtype.Out(0); returnType != typeOfError {
+ log.Println("method", mname, "returns", returnType.String(), "not error")
continue
}
s.method[mname] = &methodType{method: method, ArgType: argType, ReplyType: replyType}
@@ -339,7 +338,7 @@ func (s *service) call(server *Server, sending *sync.Mutex, mtype *methodType, r
function := mtype.method.Func
// Invoke the method, providing a new value for the reply.
returnValues := function.Call([]reflect.Value{s.rcvr, argv, replyv})
- // The return value for the method is an os.Error.
+ // The return value for the method is an error.
errInter := returnValues[0].Interface()
errmsg := ""
if errInter != nil {
4. utf8
パッケージのエラー定義の更新 (src/pkg/utf8/string.go
)
--- a/src/pkg/utf8/string.go
+++ b/src/pkg/utf8/string.go
@@ -4,6 +4,8 @@
package utf8
+import "errors"
+
// String wraps a regular string with a small structure that provides more
// efficient indexing by code point index, as opposed to byte index.
// Scanning incrementally forwards or backwards is O(1) per index operation
@@ -193,19 +195,5 @@ func (s *String) At(i int) rune {
return r
}
-// We want the panic in At(i) to satisfy os.Error, because that's what
-// runtime panics satisfy, but we can't import os. This is our solution.\n
-// error is the type of the error returned if a user calls String.At(i) with i out of range.
-// It satisfies os.Error and runtime.Error.
-type error_ string
-\n-func (err error_) String() string {
- return string(err)
-}
-\n-func (err error_) RunTimeError() {
-}
-\n-var outOfRange = error_("utf8.String: index out of range")
-var sliceOutOfRange = error_("utf8.String: slice index out of range")
+var outOfRange = errors.New("utf8.String: index out of range")
+var sliceOutOfRange = errors.New("utf8.String: slice index out of range")
コアとなるコードの解説
1. ドキュメントの更新 (doc/codelab/wiki/index.html
)
この変更は、Goのチュートリアルやドキュメントが新しいエラーモデルを反映するように更新されたことを示しています。以前は os.Error
と明示的に記述されていた箇所が、すべて error
に置き換えられています。
func (p *Page) save() error {
の戻り値の型がos.Error
からerror
に変更されたことを説明するテキスト。save
メソッドがos.Error
を返す理由としてWriteFile
の戻り値が挙げられていた箇所がerror
に変更。io.ReadFile
が[]byte
とos.Error
を返すと説明されていた箇所が[]byte
とerror
を返すように変更。loadPage
関数が*Page
とos.Error
を返すように変更されると説明されていた箇所が*Page
とerror
を返すように変更。
これらの変更は、Go言語の学習者が最初から正しいエラーハンドリングの概念に触れることができるようにするためのものです。
2. govet
ツールのメソッドシグネチャ定義の更新 (src/cmd/govet/govet.go
)
govet
はGoのコードを静的に分析し、潜在的なバグや疑わしい構造を報告するツールです。この変更は、govet
がGoの標準ライブラリで定義されている特定のインターフェースのメソッドシグネチャをチェックする際に使用する内部マップ canonicalMethods
を更新しています。
以前は、GobDecode
, MarshalJSON
, ReadByte
などのメソッドの戻り値の型として os.Error
が期待されていました。このコミットでは、これらの期待される戻り値の型がすべて error
に変更されています。
これにより、govet
はGo 1以降の新しいエラーハンドリング規約に準拠しているかを正確にチェックできるようになり、開発者が古い os.Error
を使用している場合に警告を発するなどの機能を提供できるようになります。
3. rpc
パッケージのドキュメントと型情報の更新 (src/pkg/rpc/server.go
)
rpc
(Remote Procedure Call) パッケージは、ネットワーク経由で関数を呼び出すためのメカニズムを提供します。この変更は、rpc
サービスのメソッドがエラーを返す際の規約を更新しています。
- ドキュメントの更新:
rpc
メソッドの戻り値の型がos.Error
からerror
に変更されたことが明記されています。また、クライアントが受け取るエラーがos.ErrorString
ではなく、errors.New
によって作成されたかのように見える、という説明に変わっています。これは、エラーの生成と伝播のメカニズムがerrors
パッケージに統一されたことを示唆しています。 - リフレクション型情報の更新:
rpc
パッケージは、リフレクションを使用してサービスメソッドのシグネチャを検査します。以前はos.Error
のリフレクション型 (typeOfOsError
) を事前に計算していましたが、これがerror
インターフェースのリフレクション型 (typeOfError
) に変更されています。var unusedError *error
とvar typeOfOsError = reflect.TypeOf(unusedError).Elem()
の行が削除され、代わりにvar typeOfError = reflect.TypeOf((*error)(nil)).Elem()
が追加されています。これは、error
がインターフェースであるため、reflect.TypeOf
に直接渡すことができないため、(*error)(nil)
のようにポインタ型を経由してその要素型 (Elem()
) を取得するというGoのリフレクションのイディオムを使用しています。
- メソッドシグネチャチェックの更新:
register
関数内で、登録されるメソッドの戻り値の型がtypeOfOsError
ではなくtypeOfError
と比較されるように変更されています。これにより、rpc
サーバーはGo 1のエラーハンドリング規約に準拠したメソッドのみを登録するようになります。
これらの変更により、rpc
パッケージはGo 1のエラーモデルに完全に適合し、より現代的なエラーハンドリングをサポートするようになりました。
4. utf8
パッケージのエラー定義の更新 (src/pkg/utf8/string.go
)
utf8
パッケージはUTF-8文字列の操作を提供します。この変更は、utf8.String
型の At
メソッドが範囲外のインデックスでパニックを起こす際に使用するエラー型を更新しています。
以前は、os.Error
と runtime.Error
の両方を満たすように error_
というカスタム型が定義されていました。これは、os
パッケージをインポートせずに os.Error
を満たすための回避策でした。
このコミットでは、このカスタム error_
型が削除され、代わりに標準の errors.New
関数を使用してエラーが作成されるようになりました。
import "errors"
が追加されています。error_
型の定義と、そのString()
およびRunTimeError()
メソッドが削除されています。outOfRange
とsliceOutOfRange
の変数が、error_
型のインスタンスではなく、errors.New
によって作成されたerror
型のインスタンスを保持するように変更されています。
この変更は、Go 1で error
インターフェースが組み込み型となり、errors
パッケージが提供されたことで、このようなカスタムエラー型の定義が不要になったことを示しています。これにより、コードがよりシンプルになり、標準的なエラーハンドリングのプラクティスに準拠するようになりました。