golangの日記

Go言語を中心にプログラミングについてのブログ

Go言語(golang) エラーと独自エラーの実装

golang.png


エラーは errors パッケージや fmt パッケージを使ってエラーを作成します。
それとは別で、errorインターフェースの要件を満たせば独自のエラーを作成できます。





エラーの作成

  • fmt.Errorf は fmt.Sprintf のようにフォーマットできる
  • errors.New 文字列のみでエラーを作成する
package main

import (
    "errors"
    "fmt"
)

func main() {
    {
        err := fmt.Errorf("%d: %s", 1, "Some error")

        println(err.Error()) // 1: Some error
    }

    {
        err := errors.New("Some error")

        println(err.Error()) // Some error
    }
}


エラーにファイル名や行番号を付加する

package main

import (
    "fmt"
    "runtime"
)

func main() {
    _, file, line, ok := runtime.Caller(0)
    if ok {
        err := fmt.Errorf("Some error - file: %s, line: %d", file, line)

        println(err.Error()) // Some error - file: main.go, line: 10
    }
}



独自エラーの作成方法

標準パッケージのコードを見ていると csv/reader.go など、独自のエラーが定義してあります。
ビルトイン error は以下のようにインターフェイスで定義されているので、
構造体にError関数をもたせれば独自のエラーを作ることができます。

type error interface {
    Error() string
}


独自エラーの実装と errorsパッケージ(New,Unwrap,Is,As)の使い方

package main

import (
    "errors"
    "fmt"
)

// なんのエラーなのか、比較、確認するために定義しておく
// 慣例として Err を変数名の先頭につける
// errors.New は type errorString struct を返す https://golang.org/src/errors/errors.go?s=1875:1902#L48
var (
    ErrOne       = errors.New("error 1")
    ErrTwo       = errors.New("error 2")
    ErrUnknown   = errors.New("error unknown")
)

type CustomError struct {
    // エラーが発生した行番号などを持たせておく
    N   int
    Err error
}

// fmt.Println など、出力するための関数は、内部的に error型だった場合に Error を使う
// 行番号など、エラーメッセージに付け足すための処理を書いておく
func (e *CustomError) Error() string {
    if e.Err == ErrOne {
        return fmt.Sprintf("error number %d: %v", e.N, e.Err)
    }
    if e.Err == ErrTwo {
        return fmt.Sprintf("error number %d: %v", e.N, e.Err)
    }
    return e.Err.Error()
}

// Unwrap を定義しておけば、errors.Unwrap が使える
func (e *CustomError) Unwrap() error {
    return e.Err
}

// エラーを返すだけの関数
func ReturnsAnError(n int) error {
    switch n {
    case 1:
        // 必ず & でポインターとして返す
        return &CustomError{N: 1, Err: ErrOne}
    case 2:
        return &CustomError{N: 2, Err: ErrTwo}
    default:
        return &CustomError{N: -1, Err: ErrUnknown}
    }
}

func main() {
    err := ReturnsAnError(1)

    // エラーを型変換して、なんのエラーなのか比較して特定する
    if e, ok := err.(*CustomError); ok {
        switch e.Err {
        case ErrOne:
            fmt.Printf("N: %d, %v\n", e.N, err)
        case ErrTwo:
            fmt.Printf("N: %d, %v\n", e.N, err)
        default:
            fmt.Printf("N: %d, %v\n", e.N, err)
        }
    }

    // errors.Unwrap で、error だけを取り出す。
    // 独自エラーにUnwrap関数が定義されていなければ nil が返る
    if e := errors.Unwrap(err); e != nil {
        fmt.Println(e)
    }

    // errors.As は _, ok := err.(*CustomError) と同じ
    var ce *CustomError
    fmt.Println("errors.As:", errors.As(err, &ce)) // true

    // errors.Is は e.Err == ErrOne と同じ
    if e, ok := err.(*CustomError); ok {
        fmt.Println("errors.Is:", errors.Is(e.Err, ErrOne))
    }
}