[インデックス 12672] ファイルの概要
このコミットは、Go言語のコマンドラインツール(cmd/go
)におけるエラーハンドリングの挙動を調整するものです。具体的には、go build
および go install
コマンドがエラーを報告する際に、特定の内部エラー値である errPrintedOutput
に対して詳細なエラーメッセージのラップ(追加)を行わないように変更しています。これにより、エラー出力が冗長になるのを防ぎ、より意図されたエラー報告のセマンティクスを維持します。
コミット
- コミットインデックス: 12672
- コミットハッシュ: cf0cbfd21a925b39273454c030263b5f9dfab952
- Author: Russ Cox rsc@golang.org
- Date: Fri Mar 16 16:35:16 2012 -0400
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/cf0cbfd21a925b39273454c030263b5f9dfab952
元コミット内容
cmd/go: don't add detail to errPrintedOutput
This makes the last error-reporting CL a bit less
aggressive. errPrintedOutput is a sentinel value
that should not be wrapped.
R=gri
CC=golang-dev
https://golang.org/cl/5845052
変更の背景
この変更は、以前のエラー報告に関するコミット("last error-reporting CL")の挙動を調整するために行われました。以前の変更では、go build
や go install
のようなコマンドがエラーを検出した際に、そのエラーに対して追加のコンテキスト情報(例: "go build [パッケージパス]: [元のエラー]")を付与する処理が導入されたと考えられます。
しかし、Goの内部処理には errPrintedOutput
という特殊なエラー値が存在します。この errPrintedOutput
は、エラーメッセージが既に標準出力または標準エラー出力に表示済みであることを示す「番兵(sentinel)エラー」として機能します。つまり、このエラーが返された場合、呼び出し元はそれ以上エラーメッセージを出力する必要がない、あるいは出力すべきではないというシグナルになります。
もし errPrintedOutput
が他の通常のエラーと同様にラップされてしまうと、以下のような問題が発生します。
- 冗長なエラー出力: 既に表示されたエラーに対して、さらに「go build ...: 」のようなプレフィックスが追加されてしまい、ユーザーにとって混乱を招く冗長な出力となる可能性があります。
- セマンティクスの破壊:
errPrintedOutput
が持つ「エラーは既に処理済み」というセマンティクスが失われ、エラーハンドリングロジックが意図しない挙動を示す可能性があります。
このコミットは、このような問題を回避し、cmd/go
のエラー報告をより正確で、かつ「攻撃的でない(less aggressive)」ものにするために導入されました。
前提知識の解説
Go言語のエラーハンドリング
Go言語では、エラーは組み込みの error
インターフェースによって表現されます。関数は、成功時には nil
を返し、エラー時には error
型の値を返します。
func doSomething() error {
// ... 処理 ...
if somethingWentWrong {
return errors.New("something went wrong")
}
return nil
}
エラーを処理する際には、通常 if err != nil
でエラーの有無をチェックします。
エラーのラップ (Error Wrapping)
Go 1.13以降では、fmt.Errorf
の %w
動詞を使ってエラーをラップする機能が導入されました。これにより、エラーチェーンを作成し、元のエラー情報を失うことなく、より詳細なコンテキストを追加できるようになりました。
import (
"fmt"
"os"
)
func readFile(path string) error {
_, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to read file %s: %w", path, err) // エラーをラップ
}
return nil
}
この機能は、エラーの根本原因を特定するのに非常に役立ちますが、すべてのエラーに対して無条件に適用すべきではありません。
番兵エラー (Sentinel Errors)
番兵エラーとは、特定の意味を持つグローバル変数として定義されたエラー値のことです。これらのエラーは、その値自体が特定の状態や条件を示すために使用されます。例えば、io.EOF
はファイルの終端に達したことを示す番兵エラーです。
errPrintedOutput
は、cmd/go
の内部で定義された番兵エラーの一種です。これは、エラーメッセージが既にユーザーに表示されたことを示すために使用されます。このエラーが返された場合、上位の呼び出し元は、そのエラーに対して追加の処理(特にエラーメッセージの再出力やラップ)を行うべきではありません。
番兵エラーは、その同一性(==
演算子で比較できること)が重要であり、ラップされるとこの同一性が失われる可能性があります(Go 1.13以降のerrors.Is
関数を使えばラップされても同一性をチェックできますが、このコミットが作成された2012年時点ではそのような機能はありませんでした)。そのため、番兵エラーは通常、ラップされるべきではありません。
技術的詳細
このコミットの核心は、src/cmd/go/build.go
内の build
関数と install
関数におけるエラー処理ロジックの変更です。これらの関数は、go build
および go install
コマンドの主要な処理を担当しています。
変更前は、これらの関数の defer
ステートメント内で、err != nil
であれば無条件に fmt.Errorf
を使ってエラーをラップしていました。
// 変更前 (build 関数内)
defer func() {
if err != nil {
err = fmt.Errorf("go build %s: %v", a.p.ImportPath, err)
}
}()
// 変更前 (install 関数内)
defer func() {
if err != nil {
err = fmt.Errorf("go install %s: %v", a.p.ImportPath, err)
}
}()
このロジックでは、err
が errPrintedOutput
であった場合でも、fmt.Errorf
によって「go build ...: errPrintedOutput」のような形式でラップされてしまいます。しかし、errPrintedOutput
は既にエラーが処理済みであることを示すため、このような追加のラップは不要であり、むしろ望ましくありません。
このコミットでは、この条件に && err != errPrintedOutput
を追加することで、errPrintedOutput
の場合はラップ処理をスキップするように変更しています。
// 変更後 (build 関数内)
defer func() {
if err != nil && err != errPrintedOutput { // ここが変更点
err = fmt.Errorf("go build %s: %v", a.p.ImportPath, err)
}
}()
// 変更後 (install 関数内)
defer func() {
if err != nil && err != errPrintedOutput { // ここが変更点
err = fmt.Errorf("go install %s: %v", a.p.ImportPath, err)
}
}()
この変更により、errPrintedOutput
が返された場合には、go build
や go install
のプレフィックスが追加されることなく、元のエラー報告のセマンティクスが維持されます。これは、cmd/go
がユーザーに対してエラーを報告する際の挙動をより正確かつクリーンにするための重要な改善です。
コアとなるコードの変更箇所
--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -607,7 +607,7 @@ func (b *builder) do(root *action) {
// build is the action for building a single package or command.\n func (b *builder) build(a *action) (err error) {\n \tdefer func() {\n-\t\tif err != nil {\n+\t\tif err != nil && err != errPrintedOutput {\n \t\t\terr = fmt.Errorf(\"go build %s: %v\", a.p.ImportPath, err)\n \t\t}\n \t}()\n@@ -761,7 +761,7 @@ func (b *builder) build(a *action) (err error) {\n // install is the action for installing a single package or executable.\n func (b *builder) install(a *action) (err error) {\n \tdefer func() {\n-\t\tif err != nil {\n+\t\tif err != nil && err != errPrintedOutput {\n \t\t\terr = fmt.Errorf(\"go install %s: %v\", a.p.ImportPath, err)\n \t\t}\n \t}()\n```
## コアとなるコードの解説
変更は `src/cmd/go/build.go` ファイル内の `build` 関数と `install` 関数の2箇所にあります。
それぞれの関数には、`defer` キーワードを使って遅延実行される匿名関数が定義されています。この匿名関数は、`build` または `install` 処理が完了した後に、エラーが発生していた場合にそのエラーをラップして返す役割を担っています。
変更前は、以下の条件文でした。
```go
if err != nil {
err = fmt.Errorf("go build %s: %v", a.p.ImportPath, err)
}
これは、「もし err
が nil
でなければ(つまりエラーが発生していれば)、err
を新しいエラーメッセージでラップし直す」という意味です。
変更後は、この条件文に && err != errPrintedOutput
が追加されました。
if err != nil && err != errPrintedOutput {
err = fmt.Errorf("go build %s: %v", a.p.ImportPath, err)
}
この新しい条件文は、「もし err
が nil
でなく、かつ err
が errPrintedOutput
と等しくなければ」という論理積(AND)条件になっています。
これにより、以下の挙動が保証されます。
- 通常のエラーの場合:
err
がnil
でなく、かつerrPrintedOutput
でもない場合、従来通りgo build %s: %v
またはgo install %s: %v
の形式でエラーがラップされ、より詳細なコンテキストが追加されます。 errPrintedOutput
の場合:err
がerrPrintedOutput
と等しい場合、err != errPrintedOutput
の条件がfalse
となるため、if
ブロック内のエラーラップ処理は実行されません。その結果、errPrintedOutput
はそのままの形で返され、既にエラーメッセージが出力済みであるというセマンティクスが維持されます。
この修正は、Goのツールチェインにおけるエラー報告の正確性と一貫性を向上させるための、細かではあるが重要な改善です。
関連リンク
- https://golang.org/cl/5845052 - このコミットに対応するGoのコードレビューシステム(Gerrit)のチェンジリスト。
参考にした情報源リンク
- Go言語のエラーハンドリングに関する公式ドキュメントやブログ記事 (一般的なGoのエラー処理の理解のため)
- Go言語の
fmt.Errorf
とエラーラッピングに関する情報 (Go 1.13以降の機能だが、エラーラップの概念理解のため) - Go言語における番兵エラー(Sentinel Errors)の概念に関する情報
cmd/go
のソースコードにおけるerrPrintedOutput
の定義と使用箇所 (具体的な挙動の理解のため)
[インデックス 12672] ファイルの概要
このコミットは、Go言語のコマンドラインツール(cmd/go
)におけるエラーハンドリングの挙動を調整するものです。具体的には、go build
および go install
コマンドがエラーを報告する際に、特定の内部エラー値である errPrintedOutput
に対して詳細なエラーメッセージのラップ(追加)を行わないように変更しています。これにより、エラー出力が冗長になるのを防ぎ、より意図されたエラー報告のセマンティクスを維持します。
コミット
- コミットインデックス: 12672
- コミットハッシュ: cf0cbfd21a925b39273454c030263b5f9dfab952
- Author: Russ Cox rsc@golang.org
- Date: Fri Mar 16 16:35:16 2012 -0400
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/cf0cbfd21a925b39273454c030263b5f9dfab952
元コミット内容
cmd/go: don't add detail to errPrintedOutput
This makes the last error-reporting CL a bit less
aggressive. errPrintedOutput is a sentinel value
that should not be wrapped.
R=gri
CC=golang-dev
https://golang.org/cl/5845052
変更の背景
この変更は、以前のエラー報告に関するコミット("last error-reporting CL")の挙動を調整するために行われました。以前の変更では、go build
や go install
のようなコマンドがエラーを検出した際に、そのエラーに対して追加のコンテキスト情報(例: "go build [パッケージパス]: [元のエラー]")を付与する処理が導入されたと考えられます。
しかし、Goの内部処理には errPrintedOutput
という特殊なエラー値が存在します。この errPrintedOutput
は、エラーメッセージが既に標準出力または標準エラー出力に表示済みであることを示す「番兵(sentinel)エラー」として機能します。つまり、このエラーが返された場合、呼び出し元はそれ以上エラーメッセージを出力する必要がない、あるいは出力すべきではないというシグナルになります。
もし errPrintedOutput
が他の通常のエラーと同様にラップされてしまうと、以下のような問題が発生します。
- 冗長なエラー出力: 既に表示されたエラーに対して、さらに「go build ...: 」のようなプレフィックスが追加されてしまい、ユーザーにとって混乱を招く冗長な出力となる可能性があります。
- セマンティクスの破壊:
errPrintedOutput
が持つ「エラーは既に処理済み」というセマンティクスが失われ、エラーハンドリングロジックが意図しない挙動を示す可能性があります。
このコミットは、このような問題を回避し、cmd/go
のエラー報告をより正確で、かつ「攻撃的でない(less aggressive)」ものにするために導入されました。
前提知識の解説
Go言語のエラーハンドリング
Go言語では、エラーは組み込みの error
インターフェースによって表現されます。関数は、成功時には nil
を返し、エラー時には error
型の値を返します。
func doSomething() error {
// ... 処理 ...
if somethingWentWrong {
return errors.New("something went wrong")
}
return nil
}
エラーを処理する際には、通常 if err != nil
でエラーの有無をチェックします。
エラーのラップ (Error Wrapping)
Go 1.13以降では、fmt.Errorf
の %w
動詞を使ってエラーをラップする機能が導入されました。これにより、エラーチェーンを作成し、元のエラー情報を失うことなく、より詳細なコンテキストを追加できるようになりました。
import (
"fmt"
"os"
)
func readFile(path string) error {
_, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to read file %s: %w", path, err) // エラーをラップ
}
return nil
}
この機能は、エラーの根本原因を特定するのに非常に役立ちますが、すべてのエラーに対して無条件に適用すべきではありません。
番兵エラー (Sentinel Errors)
番兵エラーとは、特定の意味を持つグローバル変数として定義されたエラー値のことです。これらのエラーは、その値自体が特定の状態や条件を示すために使用されます。例えば、io.EOF
はファイルの終端に達したことを示す番兵エラーです。
errPrintedOutput
は、cmd/go
の内部で定義された番兵エラーの一種です。これは、エラーメッセージが既にユーザーに表示されたことを示すために使用されます。このエラーが返された場合、上位の呼び出し元は、そのエラーに対して追加の処理(特にエラーメッセージの再出力やラップ)を行うべきではありません。
Web検索の結果によると、errPrintedOutput
は cmd/go
パッケージ内で使用される内部的な番兵エラーであり、コマンドが失敗したが、その出力が既にユーザーに表示されていることを示す特殊なエラーです。その目的は、関連する出力が既に提供されている場合に、go
コマンドが「exit status 1」のような冗長なエラーメッセージを印刷するのを防ぐことです。go
コマンドの主要な実行者は、このエラーを印刷しないことを認識しています。
番兵エラーは、その同一性(==
演算子で比較できること)が重要であり、ラップされるとこの同一性が失われる可能性があります(Go 1.13以降のerrors.Is
関数を使えばラップされても同一性をチェックできますが、このコミットが作成された2012年時点ではそのような機能はありませんでした)。そのため、番兵エラーは通常、ラップされるべきではありません。
技術的詳細
このコミットの核心は、src/cmd/go/build.go
内の build
関数と install
関数におけるエラー処理ロジックの変更です。これらの関数は、go build
および go install
コマンドの主要な処理を担当しています。
変更前は、これらの関数の defer
ステートメント内で、err != nil
であれば無条件に fmt.Errorf
を使ってエラーをラップしていました。
// 変更前 (build 関数内)
defer func() {
if err != nil {
err = fmt.Errorf("go build %s: %v", a.p.ImportPath, err)
}
}()
// 変更前 (install 関数内)
defer func() {
if err != nil {
err = fmt.Errorf("go install %s: %v", a.p.ImportPath, err)
}
}()
このロジックでは、err
が errPrintedOutput
であった場合でも、fmt.Errorf
によって「go build ...: errPrintedOutput」のような形式でラップされてしまいます。しかし、errPrintedOutput
は既にエラーが処理済みであることを示すため、このような追加のラップは不要であり、むしろ望ましくありません。
このコミットでは、この条件に && err != errPrintedOutput
を追加することで、errPrintedOutput
の場合はラップ処理をスキップするように変更しています。
// 変更後 (build 関数内)
defer func() {
if err != nil && err != errPrintedOutput { // ここが変更点
err = fmt.Errorf("go build %s: %v", a.p.ImportPath, err)
}
}()
// 変更後 (install 関数内)
defer func() {
if err != nil && err != errPrintedOutput { // ここが変更点
err = fmt.Errorf("go install %s: %v", a.p.ImportPath, err)
}
}()
この変更により、errPrintedOutput
が返された場合には、go build
や go install
のプレフィックスが追加されることなく、元のエラー報告のセマンティクスが維持されます。これは、cmd/go
がユーザーに対してエラーを報告する際の挙動をより正確かつクリーンにするための重要な改善です。
コアとなるコードの変更箇所
--- a/src/cmd/go/build.go
+++ b/src/cmd/go/build.go
@@ -607,7 +607,7 @@ func (b *builder) do(root *action) {
// build is the action for building a single package or command.\n func (b *builder) build(a *action) (err error) {\n \tdefer func() {\n-\t\tif err != nil {\n+\t\tif err != nil && err != errPrintedOutput {\n \t\t\terr = fmt.Errorf(\"go build %s: %v\", a.p.ImportPath, err)\n \t\t}\n \t}()\n@@ -761,7 +761,7 @@ func (b *builder) build(a *action) (err error) {\n // install is the action for installing a single package or executable.\n func (b *builder) install(a *action) (err error) {\n \tdefer func() {\n-\t\tif err != nil {\n+\t\tif err != nil && err != errPrintedOutput {\n \t\t\terr = fmt.Errorf(\"go install %s: %v\", a.p.ImportPath, err)\n \t\t}\n \t}()\n```
## コアとなるコードの解説
変更は `src/cmd/go/build.go` ファイル内の `build` 関数と `install` 関数の2箇所にあります。
それぞれの関数には、`defer` キーワードを使って遅延実行される匿名関数が定義されています。この匿名関数は、`build` または `install` 処理が完了した後に、エラーが発生していた場合にそのエラーをラップして返す役割を担っています。
変更前は、以下の条件文でした。
```go
if err != nil {
err = fmt.Errorf("go build %s: %v", a.p.ImportPath, err)
}
これは、「もし err
が nil
でなければ(つまりエラーが発生していれば)、err
を新しいエラーメッセージでラップし直す」という意味です。
変更後は、この条件文に && err != errPrintedOutput
が追加されました。
if err != nil && err != errPrintedOutput {
err = fmt.Errorf("go build %s: %v", a.p.ImportPath, err)
}
この新しい条件文は、「もし err
が nil
でなく、かつ err
が errPrintedOutput
と等しくなければ」という論理積(AND)条件になっています。
これにより、以下の挙動が保証されます。
- 通常のエラーの場合:
err
がnil
でなく、かつerrPrintedOutput
でもない場合、従来通りgo build %s: %v
またはgo install %s: %v
の形式でエラーがラップされ、より詳細なコンテキストが追加されます。 errPrintedOutput
の場合:err
がerrPrintedOutput
と等しい場合、err != errPrintedOutput
の条件がfalse
となるため、if
ブロック内のエラーラップ処理は実行されません。その結果、errPrintedOutput
はそのままの形で返され、既にエラーメッセージが出力済みであるというセマンティクスが維持されます。
この修正は、Goのツールチェインにおけるエラー報告の正確性と一貫性を向上させるための、細かではあるが重要な改善です。
関連リンク
- https://golang.org/cl/5845052 - このコミットに対応するGoのコードレビューシステム(Gerrit)のチェンジリスト。
参考にした情報源リンク
- Go言語のエラーハンドリングに関する公式ドキュメントやブログ記事 (一般的なGoのエラー処理の理解のため)
- Go言語の
fmt.Errorf
とエラーラッピングに関する情報 (Go 1.13以降の機能だが、エラーラップの概念理解のため) - Go言語における番兵エラー(Sentinel Errors)の概念に関する情報
cmd/go
のソースコードにおけるerrPrintedOutput
の定義と使用箇所 (具体的な挙動の理解のため)- Web検索: "Go errPrintedOutput sentinel error"