⭐️Goにはなぜ例外がないのか

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 と同じように変数に入れて自由に扱える。この性質を活かせばもっと簡潔に書ける、という話。