#Go1.13からエラーがラップできるようになった
Go1.13のバージョンからエラーのラップができるようになったみたいなので、自分なりにまとめてみました。
##新しく追加された関数
- func As(err error, target interface{}) bool
- func Is(err, target error) bool
- func New(text string) error
- func Unwrap(err error) error
エラーのラップ方法
まず、エラーのラップ方法はこんな感じになります
package main
import(
"errors"
"fmt"
)
var MyError = myError()
func myError() error { return errors.New("myError")}
func makeMyError() error {
return MyError
}
func makeWrappedError() error {
err := makeMyError()
return fmt.Errorf("%w", err)
}
func main() {
err := makeMyError()
fmt.Printf("%T\n", err)
wrappedErr := makeWrappedError()
fmt.Printf("%T\n", wrappedErr)
}
このようにエラーをwrapするときには fmt.Errorf("%w", err)
を使います。
実行結果は以下
*errors.errorString
*fmt.wrapError
きちんとwrapされているのがわかります。
今度はこのエラーを取り出すのにUnwrapします。
func main() {
wrappedErr := makeWrappedError()
fmt.Printf("type:%T", wrappedErr)
unWrappedErr := errors.Unwrap(wrappedErr)
fmt.Printf("type:%T", unWrappedErr)
nilErr := errors.Unwrap(unWrappedErr)
fmt.Printf("type:%T", nilErr)
}
結果は以下。
type:*fmt.wrapError
type:*errors.errorString
type:<nil>
このようにUnwrapされて行き、wrapしていないエラーをUnwrapすると、nilを返すようになります。
ここまでがエラーのラップについてです。
##Is関数の使い方
エラーのラップはわかっていただけたと思いますが、これをどうやって使うの?という話になります。
まず、今までのエラーハンドリングについておさらいします。こんな感じ
func main() {
err := makeMyError()
if err != nil {
switch err {
case MyError:
fmt.Printf("MyError: %s", err.Error())
default:
fmt.Printf("defaultError: %s", err.Error())
}
}
}
結果は以下
MyError: myError
これで、MyErrorを拾えています。ここでエラーをラップしたものに変えてみます。
func main() {
err := makeWrappedMyError()
if err != nil {
switch err {
case MyError:
fmt.Printf("MyError: %s", err.Error())
default:
fmt.Printf("defaultError: %s", err.Error())
}
}
}
結果は
defaultError: myError
となってしまし、MyErrorとして拾えていません。ここで、Is関数の登場です。
Is関数を使うとこのようになります。
func main() {
err := makeWrappedError()
if err != nil {
if errors.Is(err, MyError) {
fmt.Printf("MyError: %s", err.Error())
} else {
fmt.Printf("defaultError: %s", err.Error())
}
}
}
MyError: myError
今度は、きちんとMyErrorとして拾えました。
このように、今までのエラーハンドリングでは扱えないパターンが出てくるので、Is関数を用いる必要が出てきます。
##As関数の使い方
As関数もIs関数と使う目的は同じですが、引数を見ればわかるように自分で定義したエラーとの比較などを行うときに用います。
このような独自のエラーがあるとしましょう。
package main
import(
"fmt"
)
type marshalError struct {
msg string
code int
}
func (e *marshalError)Error() string {
return fmt.Sprintf("marsshal error :%s, code: %d", e.msg, e.code)
}
func marshal() error {
return &marshalError{msg:"hoge", code:0}
}
func main() {
err := marshal()
if e, ok := err.(*marshalError); ok {
fmt.Printf("%s", e.Error())
} else {
fmt.Printf("defaultError")
}
}
marsshal error :hoge, code: 0
これで、構造体をマーシャル(仮想で)して帰ってきたエラーがエラーがmainErrorであることを確かめられました。
しかし、このエラーがまたラップされていた場合拾えなくなってしまいます。
package main
import(
"fmt"
)
type marshalError struct {
msg string
code int
}
func (e *marshalError)Error() string {
return fmt.Sprintf("marsshal error :%s, code: %d", e.msg, e.code)
}
func marshal() error {
return &marshalError{msg:"hoge", code:0}
}
func wrapError() error {
err := marshal()
return fmt.Errorf("%w", err)
}
func main() {
err := wrapError()
if e, ok := err.(*marshalError); ok {
fmt.Printf("%s", e.Error())
} else {
fmt.Printf("defaultError")
}
}
defaultError
これを避けるためにAs関数を使います。
package main
import(
"errors"
"fmt"
)
type marshalError struct {
msg string
code int
}
func (e *marshalError)Error() string {
return fmt.Sprintf("marsshal error :%s, code: %d", e.msg, e.code)
}
func marshal() error {
return &marshalError{msg:"hoge", code:0}
}
func wrapError() error {
err := marshal()
return fmt.Errorf("%w", err)
}
func main() {
err := wrapError()
var targetErr *marshalError
if errors.As(err, &targetErr) {
fmt.Printf("%s", targetErr)
} else {
fmt.Printf("defaultError")
}
}
mainError: main error
これで、mainErrorを拾えました。
最後にこれを応用して、エラーの抽象度をあげて、上位の関数に返すようなコードを考えてみました。
package main
import(
"errors"
"fmt"
)
type User struct {
name string
email string
}
type invalidStructError struct {
original interface{} //元の構造体
err error
code int
}
func (e *invalidStructError)Error() string {
return fmt.Sprintf("original: %v, %s", e.original, e.err.Error())
}
func marshal(v interface{}) error {
err := &invalidStructError{original:v, err: errors.New("invalid Struct"), code: 0}
if err != nil{
return fmt.Errorf("marshal err: %w", err)
}
return nil
}
func main() {
val := User{name: "hoge", email: "1"}
err := marshal(val)
var target *invalidStructError
if errors.As(err, &target) {
fmt.Printf("code: %d\n", target.code)
fmt.Printf("%s\n", target.Error())
} else {
fmt.Printf("defaultError")
}
}
code: 0
original: {hoge 1}, invalid Struct
As関数を用いてtargetにerrをUnwrapしているので、codeのフィールドが扱えるようになりました。
##まとめ
このように、Go1.13ではエラーのラップができるようになりました。これにより、エラーをより上位の関数に渡すときに抽象化して渡すことができるようになります。
それに伴って、エラーのハンドリングが変わってくるので、新しい形式をうまく使いこなせると良さそうです。
まだまだgo初心者なので、間違いなどありましたらご指摘いただけると幸いです。
###参考サイト
https://cipepser.hatenablog.com/entry/go1.13-error-wrapping
https://golang.org/pkg/errors/#pkg-index