Caution

お使いのブラウザはJavaScriptが実行できない状態になっております。
当サイトはWebプログラミングの情報サイトの為、
JavaScriptが実行できない環境では正しいコンテンツが提供出来ません。
JavaScriptが実行可能な状態でご閲覧頂くようお願い申し上げます。

  1. トップページ
  2. Go辞典
  3. カスタムエラー型

カスタムエラー型

カスタムエラー型は、構造体に『Error() string』メソッドを実装することで作成します。エラーに追加の情報(エラーコード・フィールド名など)を持たせたい場合に使用します。

構文
// error インターフェースを実装する構造体を定義します。
type MyError struct {
    フィールド 型
}

// Error() string メソッドを実装します。これで error インターフェースを満たします。
func (e *MyError) Error() string {
    return "エラーメッセージ"
}

// 内包するエラーをアンラップできるようにします(任意)。
func (e *MyError) Unwrap() error {
    return e.Cause
}
実装パターン
パターン概要
Error() string の実装『error』インターフェースの唯一の要件です。このメソッドを持つ型は自動的に『error』として扱われます。
Unwrap() error の実装エラーをラップする場合に実装します。『errors.Is()』と『errors.As()』がこのメソッドを使ってチェーンを検索します。
errors.As() との組み合わせ『errors.As(err, &target)』で特定のカスタムエラー型にアクセスし、追加フィールドを取得できます。
ポインタレシーバ vs 値レシーバ一般的にポインタレシーバ(*MyError)で実装します。値レシーバでも動作しますが、nilポインタの扱いに注意が必要です。
サンプルコード
package main

import (
    "errors"
    "fmt"
)

// HTTPエラーを表すカスタムエラー型です。
type HTTPError struct {
    StatusCode int
    Message    string
    Cause      error // ラップする内包エラーです。
}

// error インターフェースを実装します。
func (e *HTTPError) Error() string {
    if e.Cause != nil {
        return fmt.Sprintf("HTTP %d: %s (caused by: %v)", e.StatusCode, e.Message, e.Cause)
    }
    return fmt.Sprintf("HTTP %d: %s", e.StatusCode, e.Message)
}

// Unwrap()を実装してエラーチェーンに対応します。
func (e *HTTPError) Unwrap() error {
    return e.Cause
}

// バリデーションエラーを表すカスタムエラー型です。
type ValidationError struct {
    Field   string
    Value   any
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation failed on '%s' (value=%v): %s", e.Field, e.Value, e.Message)
}

// リクエストを処理する関数です。
func processRequest(userID int) error {
    if userID <= 0 {
        return &ValidationError{
            Field:   "userID",
            Value:   userID,
            Message: "must be a positive integer",
        }
    }
    if userID > 1000 {
        cause := errors.New("record not found in database")
        return &HTTPError{StatusCode: 404, Message: "user not found", Cause: cause}
    }
    return nil
}

func main() {
    // バリデーションエラーの例です。
    err := processRequest(-5)
    fmt.Println(err)
    // 『validation failed on 'userID' (value=-5): must be a positive integer』と出力されます。

    // errors.As()でカスタムエラー型の情報を取得します。
    var valErr *ValidationError
    if errors.As(err, &valErr) {
        fmt.Printf("フィールド: %s\n", valErr.Field)  // 『フィールド: userID』と出力されます。
        fmt.Printf("値: %v\n", valErr.Value)           // 『値: -5』と出力されます。
    }

    // HTTPエラーの例(Unwrap対応)です。
    err = processRequest(9999)
    fmt.Println(err)
    // 『HTTP 404: user not found (caused by: record not found in database)』と出力されます。

    var httpErr *HTTPError
    if errors.As(err, &httpErr) {
        fmt.Printf("ステータスコード: %d\n", httpErr.StatusCode) // 『ステータスコード: 404』と出力されます。
    }

    // errors.Is()でUnwrapしたエラーも検索できます。
    dbErr := errors.New("record not found in database")
    wrapped := &HTTPError{StatusCode: 404, Message: "not found", Cause: dbErr}
    fmt.Println(errors.Is(wrapped, dbErr)) // 『true』と出力されます。
}
概要

カスタムエラー型は、エラーの種類だけでなく詳細情報(ステータスコード・フィールド名・元の値など)を構造化して保持できます。『errors.As()』と組み合わせることで、呼び出し元が詳細情報にアクセスできます。

カスタムエラー型のポインタ(*MyError)でエラーを生成してください。値型(MyError)でも動作しますが、『nil』との比較で予期しない動作をすることがあります。

エラーのラッピングと文脈情報の付加には『fmt.Errorf() / %w』も活用できます。エラーの判定方法については『error 型 / errors パッケージ』を参照してください。

記事の間違いや著作権の侵害等ございましたらお手数ですがまでご連絡頂ければ幸いです。