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

[インデックス 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 buildgo install のようなコマンドがエラーを検出した際に、そのエラーに対して追加のコンテキスト情報(例: "go build [パッケージパス]: [元のエラー]")を付与する処理が導入されたと考えられます。

しかし、Goの内部処理には errPrintedOutput という特殊なエラー値が存在します。この errPrintedOutput は、エラーメッセージが既に標準出力または標準エラー出力に表示済みであることを示す「番兵(sentinel)エラー」として機能します。つまり、このエラーが返された場合、呼び出し元はそれ以上エラーメッセージを出力する必要がない、あるいは出力すべきではないというシグナルになります。

もし errPrintedOutput が他の通常のエラーと同様にラップされてしまうと、以下のような問題が発生します。

  1. 冗長なエラー出力: 既に表示されたエラーに対して、さらに「go build ...: 」のようなプレフィックスが追加されてしまい、ユーザーにとって混乱を招く冗長な出力となる可能性があります。
  2. セマンティクスの破壊: 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)
    }
}()

このロジックでは、errerrPrintedOutput であった場合でも、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 buildgo 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)
}

これは、「もし errnil でなければ(つまりエラーが発生していれば)、err を新しいエラーメッセージでラップし直す」という意味です。

変更後は、この条件文に && err != errPrintedOutput が追加されました。

if err != nil && err != errPrintedOutput {
    err = fmt.Errorf("go build %s: %v", a.p.ImportPath, err)
}

この新しい条件文は、「もし errnil でなく、かつ errerrPrintedOutput と等しくなければ」という論理積(AND)条件になっています。

これにより、以下の挙動が保証されます。

  • 通常のエラーの場合: errnil でなく、かつ errPrintedOutput でもない場合、従来通り go build %s: %v または go install %s: %v の形式でエラーがラップされ、より詳細なコンテキストが追加されます。
  • errPrintedOutput の場合: errerrPrintedOutput と等しい場合、err != errPrintedOutput の条件が false となるため、if ブロック内のエラーラップ処理は実行されません。その結果、errPrintedOutput はそのままの形で返され、既にエラーメッセージが出力済みであるというセマンティクスが維持されます。

この修正は、Goのツールチェインにおけるエラー報告の正確性と一貫性を向上させるための、細かではあるが重要な改善です。

関連リンク

参考にした情報源リンク

  • 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 buildgo install のようなコマンドがエラーを検出した際に、そのエラーに対して追加のコンテキスト情報(例: "go build [パッケージパス]: [元のエラー]")を付与する処理が導入されたと考えられます。

しかし、Goの内部処理には errPrintedOutput という特殊なエラー値が存在します。この errPrintedOutput は、エラーメッセージが既に標準出力または標準エラー出力に表示済みであることを示す「番兵(sentinel)エラー」として機能します。つまり、このエラーが返された場合、呼び出し元はそれ以上エラーメッセージを出力する必要がない、あるいは出力すべきではないというシグナルになります。

もし errPrintedOutput が他の通常のエラーと同様にラップされてしまうと、以下のような問題が発生します。

  1. 冗長なエラー出力: 既に表示されたエラーに対して、さらに「go build ...: 」のようなプレフィックスが追加されてしまい、ユーザーにとって混乱を招く冗長な出力となる可能性があります。
  2. セマンティクスの破壊: 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検索の結果によると、errPrintedOutputcmd/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)
    }
}()

このロジックでは、errerrPrintedOutput であった場合でも、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 buildgo 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)
}

これは、「もし errnil でなければ(つまりエラーが発生していれば)、err を新しいエラーメッセージでラップし直す」という意味です。

変更後は、この条件文に && err != errPrintedOutput が追加されました。

if err != nil && err != errPrintedOutput {
    err = fmt.Errorf("go build %s: %v", a.p.ImportPath, err)
}

この新しい条件文は、「もし errnil でなく、かつ errerrPrintedOutput と等しくなければ」という論理積(AND)条件になっています。

これにより、以下の挙動が保証されます。

  • 通常のエラーの場合: errnil でなく、かつ errPrintedOutput でもない場合、従来通り go build %s: %v または go install %s: %v の形式でエラーがラップされ、より詳細なコンテキストが追加されます。
  • errPrintedOutput の場合: errerrPrintedOutput と等しい場合、err != errPrintedOutput の条件が false となるため、if ブロック内のエラーラップ処理は実行されません。その結果、errPrintedOutput はそのままの形で返され、既にエラーメッセージが出力済みであるというセマンティクスが維持されます。

この修正は、Goのツールチェインにおけるエラー報告の正確性と一貫性を向上させるための、細かではあるが重要な改善です。

関連リンク

参考にした情報源リンク

  • Go言語のエラーハンドリングに関する公式ドキュメントやブログ記事 (一般的なGoのエラー処理の理解のため)
  • Go言語のfmt.Errorfとエラーラッピングに関する情報 (Go 1.13以降の機能だが、エラーラップの概念理解のため)
  • Go言語における番兵エラー(Sentinel Errors)の概念に関する情報
  • cmd/go のソースコードにおける errPrintedOutput の定義と使用箇所 (具体的な挙動の理解のため)
  • Web検索: "Go errPrintedOutput sentinel error"