エラー, カスタムエラー, panicとrecover

Goには例外処理が存在しなく、関数の戻り値としてerrorインタフェースを返すことでエラーハンドリングするというルールが存在します。また、panic、recoverといった組み込み関数が用意されており、ランタイムエラーの制御に利用できます。

エラー

以下のように、多くの関数はエラー値を返すように実装されています。関数を呼び出す側は、エラーがnilかどうか判定して、nil以外であればエラー処理を行います。

package main

import (
	"fmt"
	"time"
)

func main() {
	t1, err := time.Parse(time.RFC3339, "2020-12-01T00:00:00+09:00")
	if err != nil {
		fmt.Printf("type: %T\n", err)
		fmt.Printf("value: %v\n", err)
		return
	}
	fmt.Println(t1)

	t2, err := time.Parse(time.RFC3339, "ABC")
	if err != nil {
		fmt.Printf("type: %T\n", err)
		fmt.Printf("value: %v\n", err)
		return
	}
	fmt.Println(t2)
}
2020-12-01 00:00:00 +0900 JST
type: *time.ParseError
value: parsing time "ABC" as "2006-01-02T15:04:05Z07:00": cannot parse "ABC" as "2006"

カスタムエラー

独自のエラータイプを作りたい場合、組み込みインターフェースである error interface を実装します。

type error interface {
    Error() string
}

以下、実装例です。SampleErrorという独自のエラータイプを定義しています。error interface を実装するため、Errorメソッドを実装しています。

package main

import (
	"fmt"
)

type SampleError struct {
	Field1 string
	Field2 string
}

func (e *SampleError) Error() string {
	return fmt.Sprintf("[sample error] Field1: %v Field2: %v", e.Field1, e.Field2)
}

func sampleFunc(i interface{}) (int, error) {
	switch v := i.(type) {
	case int:
		return v * 10, nil
	default:
		return 0, &SampleError{"hello", "world"}
	}

}

func main() {
	v1, err := sampleFunc(5)
	if err != nil {
		fmt.Printf("type: %T\n", err)
		fmt.Printf("value: %v\n", err)
		return
	}
	fmt.Println(v1)

	v2, err := sampleFunc("hello")
	if err != nil {
		fmt.Printf("type: %T\n", err)
		fmt.Printf("value: %v\n", err)
		return
	}
	fmt.Println(v2)
}
50
type: *main.SampleError
value: [sample error] Field1: hello Field2: world

panicとrecover

panic

配列のインデックス範囲外にアクセスするといった処理があると、panicと呼ばれる状態になり処理が終了します。

package main

import (
	"fmt"
)

func sampleFunc1() {
	fmt.Println("wakuwaku")
	a := []int{1, 2, 3}
	fmt.Println(a[3])
	fmt.Println("bank")
}

func main() {
	fmt.Println("hello")
	sampleFunc1()
	fmt.Println("world")
}
hello
wakuwaku
panic: runtime error: index out of range [3] with length 3

goroutine 1 [running]:
main.sampleFunc1()
        /tmp/sample1.go:10 +0x85
main.main()
        /tmp/sample1.go:16 +0x7e
exit status 2

組み込み関数panic を利用して、明示的にpanicを発生させることもできます。

package main

import (
	"fmt"
)

func sampleFunc2() {
	fmt.Println("wakuwaku")
	panic("panic test")
	fmt.Println("bank")
}

func main() {
	fmt.Println("hello")
	sampleFunc2()
	fmt.Println("world")
}
hello
wakuwaku
panic: panic test

goroutine 1 [running]:
main.sampleFunc2()
        /tmp/sample2.go:7 +0x95
main.main()
        /tmp/sample2.go:13 +0x7e
exit status 2

recover

panic発生後も処理を継続させたい場合、recover関数を利用します。

以下例では、deferで登録した関数内でrecoverを実行して、処理を継続させています。

package main

import (
	"fmt"
)

func sampleFunc3() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("recover: ", r)
		}
	}()

	fmt.Println("wakuwaku")
	a := []int{1, 2, 3}
	fmt.Println(a[3])
	fmt.Println("bank")
}

func main() {
	fmt.Println("hello")
	sampleFunc3()
	fmt.Println("world")
}
hello
wakuwaku
recover:  runtime error: index out of range [3] with length 3
world

参考・関連