😺
golangのerrorsにあるIs,Asを調べてみた
errorsのIs,Asとは?
- https://pkg.go.dev/errors
- errorsにはIs,Asという処理が1.13から導入されました
- goにおけるエラーの判定に使用できる処理です
errors.Isとは?
- 公式
- 比較判定できるオブジェクトならそのまま
- Isが生えているならそこで比較
- Unwrapが生えているなら次のループへ
- コードで表すと以下のようになる
package main
import (
"errors"
"fmt")
type myError struct {
s string
}
func (obj *myError) Error() string {
return ""
}
type myErrorImplementedIs struct {
s string
}
func (obj *myErrorImplementedIs) Is(err error) bool {
return true
}
func (obj *myErrorImplementedIs) Error() string {
return ""
}
func main() {
// 比較可能で同じオブジェクトを返す場合は真
{
err := &myError{s:"hoge"}
fmt.Println(errors.Is(err, err))
}
// 比較可能でもオブジェクトが違えば偽
{
err := &myError{s:"hoge"}
target := errors.New("hoge")
fmt.Println(errors.Is(err, target))
}
// オブジェクトが違っていてもerrにIsが実装されていればそれを返す
{
err := &myErrorImplementedIs{s:"hoge"}
target := errors.New("hoge")
fmt.Println(errors.Is(err, target))
}
// Unwrapが実装されていれば返り値が比較可能かIsで判定する
// errのwrapのどれかに同一のオブジェクトか真を返すIsがあれば通る
{
target := &myError{s:"hoge"}
err := fmt.Errorf("%w",target)
fmt.Println(errors.Is(err, target))
}
}
errors.Isの中身
- Isの中身が以下になります
func Is(err, target error) bool {
if target == nil {
return err == target
}
isComparable := reflectlite.TypeOf(target).Comparable()
for {
if isComparable && err == target {
return true
}
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
return true
}
if err = Unwrap(err); err == nil {
return false
}
}
}
比較可能かの判定
- 比較対象をreflectを使用して比較可能かどうかを判定
- 比較可能でない場合はruntime errorが起きるからここでチェックしている
+ 以下のURLが詳しい
+ https://golang.org/ref/spec#Comparison_operators
+ https://pod.hatenablog.com/entry/2016/07/30/204357
+ https://golang.org/ref/spec#Assignability
+ https://budougumi0617.github.io/2019/07/07/prevent-runtime-error-by-pointer/
isComparable := reflectlite.TypeOf(target).Comparable()
- errとtargetが比較可能な場合
- 最初の判定で真
if isComparable && err == target {
return true
}
errがIsメソッドを持っている場合
- errがIsメソッドを持っている場合
- errのIsにtargetを渡して比較
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
return true
}
errをUnwrapしてnilが返ればそこで終了
if err = Unwrap(err); err == nil {
return false
}
- このUnwarpも中身は上のIs判定と同様な処理をしていて
- Unwarpメソッドを保持していればそれを返す
func Unwrap(err error) error {
u, ok := err.(interface { Unwrap() error })
if !ok {
return nil
}
return u.Unwrap()
}
error.Asとは?
- errors.Asは第1引数にerr、第2引数に可能性のありそうなエラー型を渡す
- 第2に代入可能であれば代入して真を返す
- 代入ができない場合
- Asが実装されているかを調査して
- 実装されていればAsで判定する
- Unwrapが実装されているかを判定してあればerrに代入して
- Loopを続行する
package main
import (
"errors"
"fmt"
)
type myError struct {}
func (obj *myError) Error() string {
return "myError"
}
type myErrorAs struct {}
func (obj *myErrorAs) Error() string {
return "myErrorAs"
}
func (obj *myErrorAs) As(_ interface{}) bool {
return true
}
func main() {
// targetがinterfaceだと全てにmatchするので真
{
err := &myError{}
var target interface{}
fmt.Println(errors.As(err,&target))
}
// targetが同一の型なら代入可能なので真
{
err := &myError{}
var target *myError
fmt.Println(errors.As(err,&target))
}
// errがAsを実装されている場合はそこで判定
// この場合err.Asが常に真
{
err := &myErrorAs{}
var target *myError
fmt.Println(errors.As(err,&target))
}
}
errors.Asの中身
- とにかくまずいデータ渡すと即panicするという特徴があります
func As(err error, target interface{}) bool {
if target == nil {
panic("errors: target cannot be nil")
}
val := reflectlite.ValueOf(target)
typ := val.Type()
if typ.Kind() != reflectlite.Ptr || val.IsNil() {
panic("errors: target must be a non-nil pointer")
}
targetType := typ.Elem()
if targetType.Kind() != reflectlite.Interface && !targetType.Implements(errorType) {
panic("errors: *target must be interface or implement error")
}
for err != nil {
if reflectlite.TypeOf(err).AssignableTo(targetType) {
val.Elem().Set(reflectlite.ValueOf(err))
return true
}
if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) {
return true
}
err = Unwrap(err)
}
return false
}
- targetがnilだとpanic
if target == nil {
panic("errors: target cannot be nil")
}
- targetのTypeがPtrではないもしくはPtrでnilの場合panic
val := reflectlite.ValueOf(target)
typ := val.Type()
if typ.Kind() != reflectlite.Ptr || val.IsNil() {
panic("errors: target must be a non-nil pointer")
}
- targetの種類がinterface以外でエラータイプを実装してない場合panic
targetType := typ.Elem()
if targetType.Kind() != reflectlite.Interface && !targetType.Implements(errorType) {
panic("errors: *target must be interface or implement error")
}
代入可能かを判定
- 以下が処理の本体
- errのAssignableToをチェックしている
types.AssignableTo関数:第1引数の型の値を第2引数の型の変数に代入可能かどうか調べる
- 代入可能ならtargetに代入して返す
for err != nil {
if reflectlite.TypeOf(err).AssignableTo(targetType) {
val.Elem().Set(reflectlite.ValueOf(err))
return true
}
if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) {
return true
}
err = Unwrap(err)
}
まとめ
- Isが
同一
かどうかでAsがその型に代入可能
かどうかを判定する - 条件を満たさない場合errに実装されているならばIs,Asが実行される
- それでもだめな場合はUnwrapが実行されて次のerrがチェックされる
- 便利なんで使おう
Discussion