Goにはpanicという組み込み関数があります。この関数は一体なんなのか?
panicが呼ばれると、現在実行されている関数の処理が停止され、main関数までさかのぼります。
1func f() {
2 fmt.Println("hoge")
3 panic("panic")
4 fmt.Println("huga")
5}
6
7func main() {
8 fmt.Println("start")
9 f()
10 fmt.Println("end")
11}この場合の週力は以下の通り。
1start
2hoge
3panic: panic
4
5goroutine 1 [running]:
6main.f()
7 /Users/kg/go/main.go:9 +0x64
8main.main()
9 /Users/kg/go/main.go:15 +0x54
10exit status 2panic前のstartとhogeという文字列だけが表示され、それ以外は処理が中断してしまうので表示されません。
ただ、panicはrecover関数を使って復帰することができます。
1func f() {
2 defer func() {
3 if r := recover(); r != nil {
4 fmt.Println("recover:", r)
5 }
6 }()
7
8 fmt.Println("hoge")
9 panic("panic")
10 fmt.Println("huga") // 実行されない
11}
12
13func main() {
14 fmt.Println("start")
15 f()
16 fmt.Println("end") // panicからrecoverしたので実行される
17}recoverはdefer内でのみ有効であり、この場合は以下のような結果になります。
1start
2hoge
3recover: panic
4endhuga文字列はpanic後にあるので表示されませんが、end文字列はrecoverで落ちないようにしたので表示されました。ちなみにですが、recover関数はnilを返します。
Goでは1.13から%wヴァーブという、エラーを便利にラップできる機能が追加されました。
コードだとこんな感じ。
1package main
2
3import (
4 "errors"
5 "fmt"
6)
7
8type MyError struct {
9 err error
10}
11
12func (e MyError) Error() string {
13 return fmt.Sprintf("my error: %v", e.err)
14}
15
16func main() {
17 err := MyError{err: errors.New("hoge")}
18 newErr := fmt.Errorf("main failed / %w", err)
19 fmt.Println(newErr)
20 fmt.Println(errors.As(err, &MyError{}))
21}1main failed / my error: hoge
2truenewErr変数は%wを使ってエラーをラッピングしています。
ちなみに、発生したエラーがMyErrorかどうか確認するにはerrors.Asを使えばOKです。
switch文でerrの判別をするのもできなくはないですが、以下のようにエラーがネストされていると期待しない動きをし破綻します。
1package main
2
3import (
4 "fmt"
5)
6
7type MyError struct {
8 err string
9}
10
11func (e MyError) Error() string { return e.err }
12
13func f() error {
14 return MyError{"hoge"}
15}
16
17func g() error {
18 return fmt.Errorf("g failed: %w", f())
19}
20
21func main() {
22 err := g()
23 // switchで判定しようとするのはNG
24 switch err.(type) {
25 case MyError:
26 fmt.Println("is MyError")
27 default:
28 fmt.Println("unknown error type")
29 }
30}1unknown error typeこれの場合、gのエラーは
を返しているので、caseのMyErrorに引っ掛かりません。
しかし、前述の通りでこうすると期待通りの動きをしてくれます。
1if errors.As(err, &MyError{}) {
2 fmt.Println("detected MyError")
3 }1detected MyErrorこんな感じで、エラーが特定の型かどうか判定するためには、errors.Asを使って再帰的にアンラップしてあげる必要があるわけです。
また、&MyError{}のようにポインタ型で比較するのが大事で、値だとコンパイル時にエラーは出ませんが実行時にエラーが出ます。