🐱

golangのerrorsにあるIs,Asを調べてみた

6 min read

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

比較可能かの判定

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")  
}

代入可能かを判定

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

ログインするとコメントできます