エラー(error型・errorsパッケージ)

error型は、エラーの状態を表すものです。

エラーハンドリング

Goでは、関数の中でエラーが発生した時、多くの場合は戻り値としてerror型の値を返します。 関数の呼び出し側は、戻り値をif文でチェックします。 これはGoのエラーハンドリングの定番なので、押さえておきましょう。

i, err := strconv.Atoi("aaa")
if err != nil {
    fmt.Println(err)
    return
}
fmt.Println(i)
// 実行結果:
// strconv.Atoi: parsing "aaa": invalid syntax

エラーの返却

関数でエラーを返すには、いくつかの方法があります。

簡単な方法はerrors.New関数やfmt.Errorf関数の利用です。 いずれも引数にエラーを表す文字列を渡します。

errors.New

errors.New関数は、引数に指定した文字列のエラーを作成します。

package main

import (
    "errors"
    "fmt"
)

func main() {
    if err := f1(); err != nil {
        fmt.Println(err)
    }
}

func f1() error {
    return errors.New("エラーです")
}

// 実行結果:
// エラーです

fmt.Errorf

fmt.Errorf関数は、引数の内容で整形した文字列のエラーを作成します。

package main

import (
    "fmt"
)

func main() {
    if err := f1(); err != nil {
        fmt.Println(err)
    }
}

func f1() error {
    return fmt.Errorf("err: %s", "エラーです")
}

// 実行結果:
// err: エラーです

カスタム型

自前でerror型に準拠した型を実装する方法もあります。

実装する前にerror型の定義を確認しましょう。 次の通りエラーの情報を文字列で返却するErrorメソッドの実装が必要です。

type error interface {
    Error() string
}

自前でerror型に準拠した型を実装するサンプルを示します。 指定されたファイルを操作する処理でファイルが見つからない場合のエラー処理が書かれています。

package main

import (
    "fmt"
)

func main() {
    if _, err := readFile("/invalid/path"); err != nil {
        fmt.Println(err)
    }
}

func readFile(path string) (string, error) {
    // (中略)

    // ファイルが見つからない場合
    if true {
        // error型に準拠した構造体を返却
        return "", &FileNotFoundError{Path: path}
    }

    // (中略)

    return s, nil
}

// 自前のerror型 ここから
type FileNotFoundError struct {
    Path string
}

func (e *FileNotFoundError) Error() string {
    return fmt.Sprintf("%s: ファイルが見つかりません", e.Path)
}

// 自前のerror型 ここまで

// 実行結果:
// /invalid/path: ファイルが見つかりません

エラーのラップ

e.Unwrap()nil以外のエラーwを返却する場合、ewをラップするといいます。

ラップしたエラーを作成する簡単な方法はfmt.Errorf関数を使用することです。

package main

import (
    "errors"
    "fmt"
    "os"
)

func main() {
    err := openFile("/not/exist")
    // これはopenFile関数がラップしたエラー
    fmt.Println(err)
}

func openFile(name string) error {
    _, err := os.Open(name)
    if err != nil {
        // os.Open関数のエラーをラップ
        err := fmt.Errorf("ファイルを開けませんでした: %w", err)
        return err
    }
    fmt.Println("ファイルを開きました。")
    return nil
}

以降ではエラーのラップに関連した関数errors.Unwraperrors.Iserrors.Asを扱います。

errors.Unwrap

errors.Unwrap関数は、ラップしたエラーからラップする前のエラーを取り出します。

package main

import (
    "errors"
    "fmt"
    "os"
)

func main() {
    err := openFile("/not/exist")
    // これはopenFile関数がラップしたエラー
    // ファイルを開けませんでした: open /not/exist: no such file or directory
    fmt.Println(err)
    // openFile関数がラップする前のエラーを取り出す
    // open /not/exist: no such file or directory
    fmt.Println(errors.Unwrap(err))
}

func openFile(name string) error {
    _, err := os.Open(name)
    if err != nil {
        err := fmt.Errorf("ファイルを開けませんでした: %w", err)
        return err
    }
    fmt.Println("ファイルを開きました。")
    return nil
}

errors.Is

errors.Is関数は、指定した2つのエラーが等価であるかを確認します。 第一引数にラップしたエラーを指定すると、順番にアンラップして確認できます。

package main

import (
    "errors"
    "fmt"
    "os"
)

func main() {
    err := openFile("/not/exist")

    // errorの等価性を順番にアンラップ
    fmt.Println(errors.Is(err, os.ErrNotExist)) // true
    // アンラップせずに比較
    fmt.Println(err == os.ErrNotExist)          // false
}

func openFile(name string) error {
    _, err := os.Open(name)
    if err != nil {
        err := fmt.Errorf("ファイルを開けませんでした: %w", err)
        return err
    }
    fmt.Println("ファイルを開きました。")
    return nil
}

errors.As

errors.As関数は、第一引数に指定したエラーを第二引数の型に代入(型アサーション)します。 第一引数にラップしたエラーを指定すると、順番にアンラップして確認できます。

package main

import (
    "errors"
    "fmt"
    "os"
)

func main() {
    err := openFile("/not/exist")

    var e *os.PathError
    // errorの型アサーションを順番にアンラップ
    fmt.Println(errors.As(err, &e)) // true
    fmt.Println(e.Path)             // /not/exist
    // アンラップせずに型アサーション
    _, ok := err.(*os.PathError)
    fmt.Println(ok) // false
}

func openFile(name string) error {
    _, err := os.Open(name)
    if err != nil {
        err := fmt.Errorf("ファイルを開けませんでした: %w", err)
        return err
    }
    fmt.Println("ファイルを開きました。")
    return nil
}