エラーの判別

ダメなケース

同じメッセージを持つ、別々のエラーは等しくない。

err1 := errors.New("user not found")
err2 := errors.New("user not found")
fmt.Println(err1 == err2) //=> false

なので、こういったコードはうまくいかない。

package user

func Lookup() (*User, error) {
  return nil, errors.New("user not found")
}
package main

func main() {
  user, err := user.Lookup()
  if err == errors.New("user not found") {
    fmt.Fprintln(os.Stderr, "failed to lookup user")
  }
}

シンプルなエラーの判別

エラーを比較するには、エラーを生成するコードとエラーを判別するコードの間でエラーを共有する。

package user

var ErrNotFound = errors.New("user not found")

func Lookup() (*User, error) {
  return nil, ErrNotFound
}
package main

func main() {
  user, err := user.Lookup()
  if err == user.ErrNotFound {
    fmt.Fprintln(os.Stderr, "failed to lookup user")
  }
}

データをもつエラーの判別

データをもつエラーを表現するには、Error()を実装する型を定義する。errors.As()を使うと、エラーの判別をしつつ型アサーションにより内部のデータにアクセスできる。

package user

type ErrNotFound struct {
  ID int
}

func (err ErrNotFound) Error() string {
  return fmt.Sprintf("user not found: %d", err.ID)
}

func Lookup(id int) (*User, error) {
  return nil, ErrorNotFound{ID: id}
}
package main

func main() {
  user, err := user.Lookup(1)

  var notFoundErr user.ErrNotFound
  if errors.As(err, &notFoundErr) {
    fmt.Fprintf(os.Stderr, "failed to lookup user ID:%d\n", notFoundErr.ID)
  }
}