Go言語のエラー処理の仕組み② - 比較編

JavaScriptを有効にしてください

前書き

前回は、エラー処理の基本的な所を解説しました。
前回の記事:Go言語のエラー処理の仕組み① - 基本編

今回はエラー変数の比較について解説します。

エラー変数の比較

エラー変数の比較なんて、==演算子を使えばいいのでは?と思う方がいらっしゃるかも知れません。

たしかに下記のようなケースであれば==演算子でも問題なく比較できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
var ErrIsNotMikeNeko = errors.New("みけ猫ではありません!")

func IsMikeNeko(s string) error {
	if s == "みけ猫" {
		return nil
	} else {
		return ErrIsNotMikeNeko
	}
}

func main() {
	err := IsMikeNeko("ぶち猫")
	if err == ErrIsNotMikeNeko {
		fmt.Println(err)
	}
}
1
みけ猫ではありません!

この場合はErrIsNotMikeNeko変数が直接返却されているため==演算子で比較しても問題はありません。

しかし、次のように構造体の中にエラー変数がある場合はどうでしょうか。

1
2
3
4
type NekoCheckWrapError struct {
	ErrorString string
	Err         error
}

同じようにエラー変数を作成して==演算子で比較してみましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var ErrIsNotMikeNeko = errors.New("みけ猫ではありません!")

type NekoCheckWrapError struct {
	ErrorString string
	Err         error
}

func (e *NekoCheckWrapError) Error() string {
	return fmt.Sprintf("%s:%v", e.ErrorString, e.Err)
}

func IsMikeNekoWrap(s string) error {
	if s == "みけ猫" {
		return nil
	} else {
		return &NekoCheckWrapError{
			ErrorString: s,
			Err:         ErrIsNotMikeNeko,
		}
	}
}

func main() {
	err = IsMikeNekoWrap("ぶち猫")
	if err == ErrIsNotMikeNeko {
		fmt.Println(err)
	} else {
		fmt.Println("ErrIsNotMikeNekoと一致しません!")
	}
}
1
ErrIsNotMikeNekoと一致しません!

当然ながら==演算子では構造体内部のエラー変数のみと比較するわけではないので、一致しません。

このようにGo言語では構造体内部にエラー変数がある場合、エラー変数がラップされている状態であると表現します。
このラップされた状態のエラー変数に対して比較を行うために、errors.Isという関数が用意されています。

errors.Is関数を使用してエラー変数を比較する

ではerrors.Is関数を使って比較してみましょう。

1
2
3
4
5
6
7
8
func main() {
	err = IsMikeNekoWrap("ぶち猫")
	if errors.Is(err, ErrIsNotMikeNeko) {
		fmt.Println(err)
	} else {
		fmt.Println("ErrIsNotMikeNekoと一致しません!")
	}
}
1
ErrIsNotMikeNekoと一致しません!

おや…?errors.Is関数を使ってもうまく比較できていませんね🤔

これは残念ながら、NekoCheckWrapError変数にUnwrapメソッドが実装されていないため比較できないのです。

エラー変数にUnwrapメソッドを実装する

Unwrapメソッドは、構造体内部のエラー変数のみを返却する役割を持っています。

1
2
3
func (e *NekoCheckWrapError) Unwrap() error {
	return e.Err
}

errors.Is関数は、内部でこのUnwrapメソッドを呼び出してエラー変数と比較を行っています。

では、Unwrapメソッドを実装して比較してみましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func (e *NekoCheckWrapError) Unwrap() error {
	return e.Err
}

func main() {
	err = IsMikeNekoWrap("ぶち猫")
	if errors.Is(err, ErrIsNotMikeNeko) {
		fmt.Println(err)
	} else {
		fmt.Println("ErrIsNotMikeNekoと一致しません!")
	}
}
1
ぶち猫はみけ猫ではありません!

今度はちゃんと比較できました🤗

このようにGo言語では、一貫した方法でエラー変数の比較ができるようにerrors.Is関数が用意されています。
そのため、「構造体内部にエラー変数を持つ場合はUnwrapメソッドを実装しましょう」という規約がGo 1.13のバージョンから追加されました。(errors.Is関数もGo 1.13から追加されています)

このエラー変数のラップは標準パッケージでよく使われている手法なので、特にここの事情を理解していないと、比較でつまづいてしまう方がいらっしゃると思います🙄
まぁ私のことなんですが😇

最後に

今回は比較編を書いてみましたが、いかがでしたか。
Go言語ではエラー処理周りで色々な約束事があるため、比較一つとっても中々とっつきにくいです。
しかし、このような決まり事が分かってくるとGo言語でのプログラミングがもっと楽しくなるので、積極的に学んでいきましょう。

プログラム全体

今回の解説で使用したすべてのコードをのせています。
コピーしてGo Playground に貼り付ければ実行できますので、実際の動作を確認してみてください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package main

import (
	"errors"
	"fmt"
)

var ErrIsNotMikeNeko = errors.New("みけ猫ではありません!")

type NekoCheckWrapError struct {
	ErrorString string
	Err         error
}

func (e *NekoCheckWrapError) Error() string {
	return fmt.Sprintf("%sは%v", e.ErrorString, e.Err)
}

func (e *NekoCheckWrapError) Unwrap() error {
	return e.Err
}

func IsMikeNeko(s string) error {
	if s == "みけ猫" {
		return nil
	} else {
		return ErrIsNotMikeNeko
	}
}

func IsMikeNekoWrap(s string) error {
	if s == "みけ猫" {
		return nil
	} else {
		return &NekoCheckWrapError{
			ErrorString: s,
			Err:         ErrIsNotMikeNeko,
		}
	}
}

func main() {
	//-----------------------------------------------------------
	//エラー変数の比較
	//-----------------------------------------------------------
	err := IsMikeNeko("ぶち猫")
	if err == ErrIsNotMikeNeko {
		fmt.Println(err)
	}
	err = IsMikeNekoWrap("ぶち猫")
	if err == ErrIsNotMikeNeko {
		fmt.Println(err)
	} else {
		fmt.Println("ErrIsNotMikeNekoと一致しません!")
	}
	//-----------------------------------------------------------
	//errors.Is関数を使用してエラー変数を比較する
	//-----------------------------------------------------------
	err = IsMikeNekoWrap("ぶち猫")
	if errors.Is(err, ErrIsNotMikeNeko) {
		fmt.Println(err)
	} else {
		fmt.Println("ErrIsNotMikeNekoと一致しません!")
	}
}
1
2
3
みけ猫ではありません!
ErrIsNotMikeNekoと一致しません!
ぶち猫はみけ猫ではありません!

実行環境

1
2
~/$ go version
go version go1.18 linux/amd64

スポンサーリンク

共有

もふもふ
著者
もふもふ
プログラマ。汎用系→ゲームエンジニア→Webエンジニア→QAエンジニア