https://zenn.dev/tokium_dev/articles/2026-03-30-go-errors
要点
多くの言語は try-catch で例外を処理するが、Go はあえて例外を使わず、エラーを関数の戻り値として返す方式を採用している。
歴史的な背景
C言語の課題: 戻り値が1つしか返せないため、エラーを -1 で表現するなど工夫が必要だった。
例外スロー型(C++, Java, JS等)の課題: Cの課題は解決したが、新たな問題が生まれた。
- 関数のコードを読まないとその関数が例外をスローするか分からない
- 「ファイルが開けない」程度のエラーまで全部「例外」として扱われがち
- 結果として
catch (e) { /* ignore */ }のように重要なエラーが握りつぶされるコードが頻出
Goの解決策
Go は多値返却(関数から複数の値を返す)ができるため、値とエラーを分けて返却できる。
f, err := Sqrt(-1)
if err != nil {
fmt.Println(err)
}
- 関数の型定義を見るだけで「この関数はエラーを返す」と分かる(明示的)
- 致命的なエラーは
panic/recoverで別扱い → 通常エラーと致命的エラーの区別が明確
if err != nil は冗長?
Go のエラー処理に対するよくある批判として「if err != nil を何度も書かなきゃいけない」というものがある。
// 愚直に書くとこうなる(冗長に見える)
_, err = fd.Write(p0[a:b])
if err != nil {
return err
}
_, err = fd.Write(p1[c:d])
if err != nil {
return err
}
_, err = fd.Write(p2[e:f])
if err != nil {
return err
}
Go 開発者の Rob Pike は Errors are values で「これは冗長なんじゃなくて、書き方が悪いだけだ」と反論している。エラーはただの値なので、変数に保持しておいて最後にまとめて処理すればいい。
// Go らしい書き方: エラーを構造体に溜めて最後にチェック
ew := &errWriter{w: fd}
ew.write(p0[a:b]) // 内部でエラーがあれば ew.err に記録し、以降の write は何もしない
ew.write(p1[c:d])
ew.write(p2[e:f])
if ew.err != nil {
return ew.err // 最後にまとめて1回だけチェック
}
要するに、冗長に見えるのは try-catch 的な「エラーが出たら即処理」という発想をそのまま Go に持ち込んでいるから。Go では「エラーは値」なので、int や string と同じように変数に入れて自由に扱える。この性質を活かせばもっと簡潔に書ける、という話。