エラー(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
を返却する場合、e
はw
をラップするといいます。
ラップしたエラーを作成する簡単な方法は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.Unwrap
・errors.Is
・errors.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
}