kg-icon
Go言語100Tips - 7章
2025-10-12

panicについて

Goにはpanicという組み込み関数があります。この関数は一体なんなのか?

panicが呼ばれると、現在実行されている関数の処理が停止され、main関数までさかのぼります。

go
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}

この場合の週力は以下の通り。

text
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 2

panic前のstartとhogeという文字列だけが表示され、それ以外は処理が中断してしまうので表示されません。

ただ、panicはrecover関数を使って復帰することができます。

go
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内でのみ有効であり、この場合は以下のような結果になります。

text
1start
2hoge
3recover: panic
4end

huga文字列はpanic後にあるので表示されませんが、end文字列はrecoverで落ちないようにしたので表示されました。ちなみにですが、recover関数はnilを返します。

エラーラッピングについて

Goでは1.13から%wヴァーブという、エラーを便利にラップできる機能が追加されました。

コードだとこんな感じ。

go
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}
text
1main failed / my error: hoge
2true

newErr変数は%wを使ってエラーをラッピングしています。

ちなみに、発生したエラーがMyErrorかどうか確認するにはerrors.Asを使えばOKです。

switch文でerrの判別をするのもできなくはないですが、以下のようにエラーがネストされていると期待しない動きをし破綻します。

go
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}
text
1unknown error type

これの場合、gのエラーは

  • MyErrorをラップしたエラー

を返しているので、caseのMyErrorに引っ掛かりません。

しかし、前述の通りでこうすると期待通りの動きをしてくれます。

go
1if errors.As(err, &MyError{}) {
2		fmt.Println("detected MyError")
3	}
text
1detected MyError

こんな感じで、エラーが特定の型かどうか判定するためには、errors.Asを使って再帰的にアンラップしてあげる必要があるわけです。

また、&MyError{}のようにポインタ型で比較するのが大事で、値だとコンパイル時にエラーは出ませんが実行時にエラーが出ます。