Goのosパケージのerrorを1.12と1.13で比較する
Goの1.13でGo2で提案されていたerrorsの変更が入り(全部ではない?)、ErrorValueFAQに移行方法などが書いてあります。その中でosパッケージに触れてありました。移行サンプルとして挙げられているわけではないですが、独自の型を作成しているライブラリがどうやって移行しているのか気になったので調べてみました。
調べてみた結果、なんか複雑になったな、というのが感想です...
今回のerrorの変更と独自エラー型との相性はあんまりよくない気がします。
前の記事では(Goによる並行処理)ではエラーは独自エラーでラップしたほうがいいという感じでしたが...
ErrorValueFAQにも下記のようにエラーをラップすべきかというのは2つのトレードオフであると書かれています。 そして、errorsは中間的な選択だそうです。
I'm writing new code, with no clients. Should I wrap returned errors or not? Since you have no clients, you aren't constrained by backwards compatibility. But you still need to balance two opposing considerations: Giving client code access to underlying errors can help it make decisions, which can lead to better software. Every error you expose becomes part of your API: your clients may come to rely on it, so you can't change it. For each error you return, you have to weigh the choice between helping your clients and locking yourself in. Of course, this choice is not unique to errors; as a package author, you make many decisions about whether a feature of your code is important for clients to know or an implementation detail. With errors, though, there is an intermediate choice: you can expose error details to people reading your code's error messages without exposing the errors themselves to client code. One way to do that is to put the details in a string using fmt.Errorf with %s or %v. Another is to write a custom error type, add the details to the string returned by its Error method, and avoid defining an Unwrap method.
1.12
package os import ( "errors" ) var ( ErrPermission = errors.New("permission denied") ) type SyscallError struct { Syscall string Err error } func (e *SyscallError) Error() string { return e.Syscall + ": " + e.Err.Error() } func NewSyscallError(syscall string, err error) error { if err == nil { return nil } return &SyscallError{syscall, err} } func IsPermission(err error) bool { return isPermission(err) } // underlyingError returns the underlying error for known os error types. func underlyingError(err error) error { switch err := err.(type) { case *PathError: return err.Err case *LinkError: return err.Err case *SyscallError: return err.Err } return err }
// +build aix darwin dragonfly freebsd js,wasm linux nacl netbsd openbsd solaris package os import "syscall" func isPermission(err error) bool { err = underlyingError(err) return err == syscall.EACCES || err == syscall.EPERM || err == ErrPermission }
1.13
package os import ( "internal/oserror" ) var ( ErrPermission = errPermission() // "permission denied" ) func errPermission() error { return oserror.ErrPermission } type SyscallError struct { Syscall string Err error } func (e *SyscallError) Error() string { return e.Syscall + ": " + e.Err.Error() } func (e *SyscallError) Unwrap() error { return e.Err } func NewSyscallError(syscall string, err error) error { if err == nil { return nil } return &SyscallError{syscall, err} } func IsPermission(err error) bool { return underlyingErrorIs(err, ErrPermission) } func underlyingErrorIs(err, target error) bool { // Note that this function is not errors.Is: // underlyingError only unwraps the specific error-wrapping types // that it historically did, not all errors.Wrapper implementations. err = underlyingError(err) if err == target { return true } // To preserve prior behavior, only examine syscall errors. e, ok := err.(syscallErrorType) return ok && e.Is(target) } // underlyingError returns the underlying error for known os error types. func underlyingError(err error) error { switch err := err.(type) { case *PathError: return err.Err case *LinkError: return err.Err case *SyscallError: return err.Err } return err }
// +build !plan9 package os import "syscall" type syscallErrorType = syscall.Errno
package oserror import "errors" var ( ErrPermission = errors.New("permission denied") )
package syscall import ( "internal/oserror" ) type Errno uintptr func (e Errno) Is(target error) bool { switch target { case oserror.ErrPermission: return e == EACCES || e == EPERM case oserror.ErrExist: return e == EEXIST || e == ENOTEMPTY case oserror.ErrNotExist: return e == ENOENT } return false }
ちなみに、PathErrorはUnwap
メソッドが増えているだけです。
1.12
type PathError struct { Op string Path string Err error } func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
1.13
type PathError struct { Op string Path string Err error } func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() } func (e *PathError) Unwrap() error { return e.Err }