[インデックス 10180] ファイルの概要
本解説は、Go言語の標準ライブラリにerrorsパッケージが導入されたコミット(インデックス10180)について詳細に説明します。このコミットは、Go言語におけるエラーハンドリングの基本的なメカニズムを確立する上で重要な一歩となりました。
コミット
このコミットは、Go言語の標準ライブラリにerrorsパッケージを新規追加するものです。現時点ではerrors.New関数のみを提供しており、これにより任意の文字列からerror型の値を生成できるようになります。これは、Go言語におけるエラー表現の基礎を築くものです。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e4ae30f5f55b6b6ba2cb9a0c0e3b11d42dc162ee
元コミット内容
commit e4ae30f5f55b6b6ba2cb9a0c0e3b11d42dc162ee
Author: Russ Cox <rsc@golang.org>
Date: Tue Nov 1 21:46:59 2011 -0400
errors: new package
The only function is errors.New, at least for now.
R=r, gustavo, adg, r
CC=golang-dev
https://golang.org/cl/5321061
変更の背景
Go言語では、エラーは戻り値として扱われることが一般的であり、errorという組み込みインターフェースによって表現されます。このインターフェースは、Error() stringというメソッドを一つだけ持ち、エラーの詳細を文字列として返すことを期待します。
このコミット以前は、開発者がカスタムエラーを定義する際には、errorインターフェースを実装する独自の型を定義する必要がありました。しかし、単純なエラーメッセージを返すだけでよい場合でも、毎回新しい型を定義するのは冗長であり、共通のユーティリティ関数が求められていました。
errorsパッケージの導入、特にerrors.New関数の提供は、この問題を解決するために行われました。これにより、開発者は簡潔にエラーメッセージを含むerror型の値を生成できるようになり、Go言語におけるエラーハンドリングの基本的なパターンが確立されました。これは、Go言語のエラーハンドリング哲学である「エラーは値である」という考え方を具現化するものです。
前提知識の解説
Go言語におけるエラーハンドリング
Go言語では、例外処理のメカニズム(try-catchなど)は存在せず、エラーは関数の戻り値として明示的に扱われます。慣例として、関数は通常の結果とerror型の戻り値をペアで返します。errorがnilであれば成功、nilでなければエラーが発生したことを意味します。
func doSomething() (resultType, error) {
// ... 処理 ...
if somethingWentWrong {
return zeroValue, errors.New("何らかのエラーが発生しました")
}
return actualResult, nil
}
errorインターフェース
Go言語のerrorは、以下のように定義された組み込みインターフェースです。
type error interface {
Error() string
}
このインターフェースを実装する任意の型は、error型として扱われます。Error()メソッドは、エラーに関する人間が読める形式の文字列を返します。
errors.Newの必要性
errors.New関数が導入される前は、単純なエラーメッセージを返すためだけに、以下のようなカスタムエラー型を定義する必要がありました。
type myError string
func (e myError) Error() string {
return string(e)
}
func someFunction() error {
return myError("これはカスタムエラーです")
}
errors.Newは、このような定型的なコードを不要にし、より簡潔にエラーを生成する手段を提供します。
技術的詳細
errorsパッケージは、errorインターフェースを実装するerrorStringという非公開の構造体を定義し、そのインスタンスを返すNew関数を提供します。
errorString構造体
errorStringは、エラーメッセージを保持するためのシンプルな構造体です。
type errorString struct {
s string
}
この構造体は、errorインターフェースのError()メソッドを実装しています。
func (e *errorString) Error() string {
return e.s
}
これにより、errorString型のポインタ(*errorString)はerrorインターフェースを満たします。
errors.New関数
errors.New関数は、引数として受け取った文字列をerrorString構造体のsフィールドに格納し、そのポインタをerror型として返します。
func New(text string) error {
return &errorString{text}
}
この設計により、errors.New("some error")と呼び出すだけで、"some error"というメッセージを持つerror型の値が生成されます。
TestNewEqualの意図
errors_test.goに含まれるTestNewEqualテストは、errors.Newが呼び出されるたびに新しいerrorインスタンスを生成することを確認しています。
if New("abc") == New("abc") {
t.Errorf(`New("abc") == New("abc")`)
}
このテストは、同じ文字列を引数にしても、New関数が異なるメモリアドレスを持つ新しいerrorStringインスタンスを返すため、それらが等しくないことを検証しています。これは、Go言語における値の比較の基本的な挙動と、errors.Newが新しいエラーオブジェクトを生成するという設計意図を示しています。エラーは値として扱われますが、その同一性はポインタの比較によって決まるため、異なる呼び出しで生成されたエラーはたとえメッセージが同じでも異なるオブジェクトとして扱われます。
コアとなるコードの変更箇所
このコミットでは、主に以下のファイルが新規作成または変更されました。
src/pkg/Makefile
errorsパッケージをビルドシステムに追加するために、DIRS変数にerrorsが追加されました。
--- a/src/pkg/Makefile
+++ b/src/pkg/Makefile
@@ -75,6 +75,7 @@ DIRS=\
encoding/git85\
encoding/hex\
encoding/pem\
+ errors\
exec\
exp/ebnf\
exp/ebnflint\
src/pkg/errors/Makefile (新規作成)
errorsパッケージのビルド設定ファイルです。errors.goをコンパイル対象として指定しています。
--- /dev/null
+++ b/src/pkg/errors/Makefile
@@ -0,0 +1,11 @@
+# Copyright 2011 The Go Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+include ../../Make.inc
+
+TARG=errors
+GOFILES=\
+ errors.go\
+
+include ../../Make.pkg
src/pkg/errors/errors.go (新規作成)
errorsパッケージの本体となるファイルです。errorString型とNew関数が定義されています。
--- /dev/null
+++ b/src/pkg/errors/errors.go
@@ -0,0 +1,20 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package errors implements functions to manipulate errors.
+package errors
+
+// New returns an error that formats as the given text.
+func New(text string) error {
+ return &errorString{text}
+}
+
+// errorString is a trivial implementation of error.
+type errorString struct {
+ s string
+}
+
+func (e *errorString) Error() string {
+ return e.s
+}
src/pkg/errors/errors_test.go (新規作成)
errorsパッケージのテストファイルです。New関数の挙動とError()メソッドの動作を検証しています。
--- /dev/null
+++ b/src/pkg/errors/errors_test.go
@@ -0,0 +1,33 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package errors_test
+
+import (
+ . "errors"
+ "testing"
+)
+
+func TestNewEqual(t *testing.T) {
+ // Different allocations should not be equal.
+ if New("abc") == New("abc") {
+ t.Errorf(`New("abc") == New("abc")`)
+ }
+ if New("abc") == New("xyz") {
+ t.Errorf(`New("abc") == New("xyz")`)
+ }
+
+ // Same allocation should be equal to itself (not crash).
+ err := New("jkl")
+ if err != err {
+ t.Errorf(`err != err`)
+ }
+}
+
+func TestErrorMethod(t *t.T) {
+ err := New("abc")
+ if err.Error() != "abc" {
+ t.Errorf(`New("abc").Error() = %q, want %q`, err.Error(), "abc")
+ }
+}
コアとなるコードの解説
src/pkg/errors/errors.go
このファイルはerrorsパッケージの心臓部です。
package errors
// New returns an error that formats as the given text.
func New(text string) error {
return &errorString{text}
}
// errorString is a trivial implementation of error.
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
package errors: このファイルがerrorsパッケージに属することを示します。func New(text string) error:New関数は、textという文字列を引数に取り、errorインターフェース型を返します。return &errorString{text}: ここが最も重要な部分です。errorStringという構造体の新しいインスタンスを生成し、そのsフィールドに引数で渡されたtextを格納します。そして、そのインスタンスへのポインタを返します。*errorString型はError()メソッドを実装しているため、errorインターフェースを満たします。
type errorString struct { s string }:errorStringは、エラーメッセージ(文字列s)を保持するためのシンプルな構造体です。この構造体はパッケージ外からは直接アクセスできない(小文字で始まるため)非公開の型です。
func (e *errorString) Error() string:- これは
errorString型がerrorインターフェースを実装するためのメソッドです。 e.s、つまりerrorStringインスタンスが保持する文字列をそのまま返します。これにより、errorインターフェースの契約が満たされます。
- これは
src/pkg/errors/errors_test.go
このテストファイルは、errorsパッケージの基本的な機能が正しく動作することを確認します。
package errors_test
import (
. "errors"
"testing"
)
func TestNewEqual(t *testing.T) {
// Different allocations should not be equal.
if New("abc") == New("abc") {
t.Errorf(`New("abc") == New("abc")`)
}
if New("abc") == New("xyz") {
t.Errorf(`New("abc") == New("abc")`)
}
// Same allocation should be equal to itself (not crash).
err := New("jkl")
if err != err {
t.Errorf(`err != err`)
}
}
func TestErrorMethod(t *testing.T) {
err := New("abc")
if err.Error() != "abc" {
t.Errorf(`New("abc").Error() = %q, want %q`, err.Error(), "abc")
}
}
import . "errors":errorsパッケージをインポートし、そのエクスポートされた識別子(この場合はNew関数)をパッケージ名なしで直接使用できるようにします。テストコードでよく見られるパターンです。TestNewEqual:New("abc") == New("abc")の比較がtrueにならないことを検証しています。これは、New関数が呼び出されるたびに新しい*errorStringインスタンスがヒープに割り当てられるため、たとえ同じ文字列を渡しても、異なるメモリアドレスを持つオブジェクトが生成されることを確認しています。Go言語では、インターフェース値の比較は、その内部の動的な型と値(ポインタの場合はポインタ値)が等しいかどうかで行われます。New("abc") == New("xyz")も同様にfalseになることを検証しています。err := New("jkl"); if err != errは、同じ変数に対する比較が常にtrueであることを確認しており、これは基本的な健全性チェックです。
TestErrorMethod:New("abc").Error()が期待通り"abc"という文字列を返すことを検証しています。これは、errorString型がError()メソッドを正しく実装していることを確認するものです。
これらのコードは、Go言語におけるエラーハンドリングの基本的な構成要素を非常にシンプルかつ効果的に提供しています。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/e4ae30f5f55b6b6ba2cb9a0c0e3b11d42dc162ee
- Go Code Review (CL) ページ: https://golang.org/cl/5321061
参考にした情報源リンク
- Go言語公式ドキュメント - Errors: https://go.dev/blog/error-handling-and-go (Go言語のエラーハンドリングに関する公式ブログ記事)
- Go言語公式ドキュメント - The Go Programming Language Specification (Errors): https://go.dev/ref/spec#Errors (Go言語仕様におけるエラーの定義)
- Go言語公式ドキュメント - errorsパッケージ: https://pkg.go.dev/errors (errorsパッケージの公式ドキュメント)
- Go言語公式ドキュメント - Effective Go (Errors): https://go.dev/doc/effective_go#errors (Effective Goにおけるエラーハンドリングのベストプラクティス)