エラー
Go言語ではエラーを例外ではなく関数の戻り値として返します。
f, err := os.Open("file.txt")
if err != nil {
return err
}
// ここ以降は正常にファイルが開けている前提で処理できるerror型
errorは組み込みインターフェースです。Error() stringメソッドを持つ型であれば、errorとして扱えます。
type error interface {
Error() string
}エラーの生成
固定文言のエラー
動的な値を含まない静的なエラーにはerrors.Newを使用します。特定のエラーとして呼び出し側で判定できるよう、パッケージレベルの変数として定義する用途にも適しています。
var ErrNotFound = errors.New("not found")動的な値を含むエラー
実行時の数値や文字列をメッセージに埋め込む場合はfmt.Errorfを使用します。
return fmt.Errorf("item %d not found", itemID)下流エラーのラップ
発生したエラーに上位の文脈を追加しつつ、元のエラー値を辿れるようにする場合はfmt.Errorfで書式指定子%wを指定します。メッセージに文言だけ足す場合は%vを指定します。
return fmt.Errorf("open %s: %w", path, err)%wでラップしたエラーは、errors.Isを用いて内部のエラーと一致するか判定できます。%vではメッセージに文言は残りますが、元のエラー値は辿れないため一致しません。
var ErrNotFound = errors.New("not found")
wrapped := fmt.Errorf("load config: %w", ErrNotFound)
fmt.Println(errors.Is(wrapped, ErrNotFound)) // true
textOnly := fmt.Errorf("load config: %v", ErrNotFound)
fmt.Println(errors.Is(textOnly, ErrNotFound)) // false独自のエラー型
構造体にError() stringメソッドを実装すると、組み込みのerrorインターフェースを満たし、error型の戻り値として返せるようになります。エラーメッセージだけでなく、失敗した入力値やエラーコードなどをフィールドに保持させたい場合に適しています。
type QueryError struct {
Query string
Err error
}
func (e *QueryError) Error() string {
return fmt.Sprintf("query %q: %v", e.Query, e.Err)
}error型のゼロ値
errorのゼロ値はnilであり、err == nilであれば処理が成功したことを表します。非nilのエラー値が返された場合は失敗として扱います。
func mayFail(ok bool) error {
if ok {
return nil // 成功
}
return errors.New("failed")
}
if err := mayFail(true); err != nil {
// 失敗処理
} else {
// 成功処理
}具象型のnilポインタをerrorとして返すと、ポインタはnilでもインターフェースに型が残るためerr != nilになります。成功を返すときはnilをそのまま返します。
func load() error {
var perr *os.PathError
return perr // perrはnilだが、戻り値のerrorは非nil
}panic
panicは実行中の関数の処理を中断し、登録されたdeferを実行しながら呼び出し元の関数を順に遡ります。途中でrecoverで回復されなければ、プログラム全体が強制終了します。整数のゼロ除算やスライスの範囲外アクセスなどによりランタイムが自動的に発生させるほか、組み込み関数panicで明示的に発生させることもできます。明示的なpanicは、初期化の失敗で起動を継続できない場合や、到達してはならない分岐に達した場合など、続行不可能な異常に限って使用します。
func mustPositive(n int) int {
if n <= 0 {
panic("unreachable: positive number required")
}
return n
}recover
recoverは、panicによってプログラムが強制終了するのを防ぐ組み込み関数です。必ずdeferで登録した関数の内部で直接呼び出します。通常時はnilを返しますが、panicの発生中に呼び出すとパニックの伝播を止め、panicに渡された値を取得します。recoverを実行すると、panicが発生した箇所以降の処理は中断されたまま終了し、deferを登録した関数が呼び出し元へ戻ります。名前付き戻り値と組み合わせることで、パニックを捕捉して呼び出し元へerrorとして返すことができます。なお、recoverが有効なのは同一ゴルーチン内で発生したpanicに限られます。また、Go言語1.21以降では、panicにnilが渡された場合でもrecoverは非nilの値を返します。
func safeDivide(a, b int) (res int, err error) {
defer func() {
if r := recover(); r != nil {
fmt.Println("recovered:", r)
err = fmt.Errorf("panic: %v", r)
}
}()
return a / b, nil
}
_, err := safeDivide(10, 0)
fmt.Println(err) // panic: runtime error: integer divide by zero