Go2 Error values
This slide is about the improvements to the error inspection and printing.
The error inspection & printing
doesn't change the language ifself (almost)
This package will be incorporated into the standard library's errors package in Go 1.13
Four ways to test errors
Four ways to test errors
Sentinel errors (value equality) err == io.ErrUnexpectedEOF
Error structs/types (type assertion or type switch) pe, ok := err.(*os.PathError)
Error-checking predicate function os.IsNotExit(err)
String search strings.Contains(err.Error(), "wtf")
var ErrUnexpectedEOF = errors.New("unexpected EOF")
Errors are values. We can test for equality with sentinel errors like io.EOF
if err == io.ErrUnexpectedEOF { ... }
To provide more information about this error, We can define a new type implements the error
interface. (Error types)
type PathError struct { Op string Path string Err error // wrapping , the cause}
if pe,ok := err.(*os.PathError); ok { ... pe.Path ... }
os.PathError
is a struct that includes a pathname. Programs can extract information from this error by using type assertion.
// IsExist returns a boolean indicating whether the error is known to report// that a file or directory already exists. It is satisfied by ErrExist as// well as some syscall errors.func IsExist(err error) bool { return isExist(err)}// IsNotExist returns a boolean indicating whether the error is known to// report that a file or directory does not exist. It is satisfied by// ErrNotExist as well as some syscall errors.func IsNotExist(err error) bool { return isNotExist(err)}
os.IsNotExist
check for a specific kind of error, doing limited unwrapping
if err != nil { return fmt.Errorf("write err: %v",err)}strings.Contains(err.Error(), "wtf")
If callers need to test for specific errors, the callers should do substring searches in this error text (err.Error()
)
IMHO, It wouldn't better than first third approaches
Go1 only supports string message of errors (func (e error) Error() string
)
Go2's goal is making errors more informative for both programs and people.
Inspecting errors
github.com/pkg/errors
Formatting errors helps more readable information of errors for people
Stack Frame is for location information (file name, line number) of errors
write users database: more detail here mypkg/db.Open /path/to/database.go:111 - call myserver.Method: google.golang.org/grpc.Invoke /path/to/grpc.go:222 - dial myserver:3333: net.Dial /path/to/net/dial.go:333 - open /etc/resolv.conf: os.Open /path/to/os/open.go:444 - permission denied
write users database: call myserver.Method: \ dial myserver:3333: open /etc/resolv.conf: permission denied
WriteError
provides "write users database", wraps RPCError
RPCError
provides "call myserver.Method", wraps net.OpError
net.OpError
provides "dial myserver:333", wraps os.PathError
os.PathError
provides "open /etc/resolv.conf", wraps syscall.EPERM
syscall.EPERM
provides "permission denied"
For inspecting (chaining errors), Go2 supports Is
and As
functions.
Wrapper
interfacetype Wrapper interface { // Unwrap returns the next error in the error chain. // If there is no next error, Unwrap returns nil. Unwrap() error}
An error that wraps another error should implement Wrapper
by defining an Unwrap
method
// Unwrap returns the result of calling the Unwrap method on err, if err implements Unwrap.// Otherwise, Unwrap returns nil.func Unwrap(err error) error
Is
function// Is reports whether any error in err's chain matches target.//// An error is considered to match a target if it is equal to that target or if// it implements a method Is(error) bool such that Is(target) returns true.func Is(err, target error) bool
The Is
is equivalent of if err == ErrSomething
, but it checks the chain of errors.
The Is
follows the chain of errors by calling Unwrap
.
It is intented to be used insteads of equality for matching sentinel errors.
An error type can implement func Is(error) bool
to override default behavior.
As
function// As finds the first error in err's chain that matches the type to which target// points, and if so, sets the target to its value and returns true. An error// matches a type if it is assignable to the target type, or if it has a method// As(interface{}) bool such that As(target) returns true. As will panic if target// is not a non-nil pointer to a type which implements error or is of interface type.//// The As method should set the target to its value and return true if err// matches the type to which target points.func As(err error, target interface{}) bool
The As
function is equivalent of the err, ok := err.(ErrSomething)
,but it checks the chain of errors.
It's intented to be used insteads of type assertion or type switch for the Error type.
An error type can implement func As(target interface{}) bool
to override default behavior.
As
functiontype Temporary interface { Temporary() bool}
fe := &FileError{err: FileNotFound} // has Temporary() methode := &ConfigError{err: we}var temp Temporaryif xerrors.As(e, &temp) == false { t.Fatalf("not found: %v", e)}t.Logf("temp.Temporary: %v", temp.Temporary())if xerrors.Is(e, FileNotFound) { t.Logf("err has FileNotFound")}
// Opaque returns an error with the same error formatting as err// but that does not match err and cannot be unwrapped.func Opaque(err error) error
The Opaque function hides a wrapped error from programmatic inspection.
Formatter can be implemented by errors. Printer designed to allow fmt
or i18n or customs.
type Formatter interface { error // FormatError prints the receiver's first error and returns the next error to be formatted, if any. FormatError(p Printer) (next error)}
type Printer interface { // Print appends args to the message output. Print(args ...interface{}) // Printf writes a formatted string. Printf(format string, args ...interface{}) // Detail reports whether error detail is requested. Detail() bool}
func (e WriteError) FormatError(p xerrors.Printer) error { p.Print(e.Error()) if p.Detail() { e.frame.Format(p) } return e.Unwrap()}
// go1.12func (e WriteError) Format(f fmt.State, c rune) { // implements fmt.Formatter xerrors.FormatError(e, f, c) // will call e.FormatError}
fmt.Printf("Error: %+v",err)
Use %+v
to print error in detailed format (multi-line)
write users database: mypkg/db.Open /path/to/database.go:111 - open /etc/resolv.conf: os.Open /path/to/os/open.go:444
Use %w
if you want to expose the underlying error to your callers.
//go1.13e := fmt.Errorf("more info: %w", err) // err is sql.ErrTxDoneif errors.Is(e,sql.ErrTxDone) { ...
FormatError
and Unwrap
methods if the last argument is an error err and the format string ends with : %s
, : %v
, or : %w
The Frame type holds location information: the function name, file and line of a single stack frame.
type Frame struct {}// Format prints the stack as error detail.func (f Frame) Format(p Printer) { ... }func Caller(skip int) Frame { ... }
func (e WriteError) FormatError(p errors.Printer) error { p.Print(e.Error()) if p.Detail() { e.frame.Format(p) } return e.Unwrap()}
Programs
Code
and Details
to recover or do something else.Code
by SentinelError? (io.EOF
)Details
by ErrorStruct? (os.PathError
)Users
Your request is not accepted
)interface { Error() string }
Operators
*os.PathError -> *syscall.EPERM
It's inspired by Error handling in Upspin and uses go2 error value features.
// Error defines a application errortype Error struct { // Kind is the kind of this error Kind Kind json:"kind" // Human-readable message Message string json:"message" // Logical operation. usually the name of method. Op Op json:"op" // Nested error Err error json:"-" frame errors.Frame}
func (e *Error) Unwrap() error { return e.Err}
func (e *Error) FormatError(p Printer) error { p.Print(e.Error()) if p.Detail() { p.Print(e.Message) } e.frame.Format(p) return e.Unwrap()}
// +build !go1.13func (e *Error) Format(f fmt.State, c rune) { // implements fmt.Formatter xerrors.FormatError(e, f, c) // will call e.FormatError}
func (e *Error) Error() string { var b strings.Builder if e.Kind != "" { fmt.Fprintf(&b, "[%s]", e.Kind) } if e.Op != "" { if e.Kind != "" { b.WriteString(": ") } fmt.Fprintf(&b, "%s", e.Op) } return b.String()}
// Kind defines the kind of errortype Kind string // or uint8
Other Kind = iota // Unclassified error. This value is not printed in the error message.Invalid // Invalid operation for this type of item.Permission // Permission denied.IO // External I/O error such as network failure.Exist // Item already exists.NotExist // Item does not exist.
google.rpc.Code.NOT_FOUND
error code and tells the client which specific resource was not found. eration
// Op describes an operationtype Op string
client.Lookup
, dir/server.Glob
const serverDelete errors.Op = "server/delete"
E
functionfunc E(args ...interface{}) error { if len(args) == 0 { panic("call to errors.E with no arguments") } e := &Error{} for _, arg := range args { switch arg := arg.(type) { case Kind: e.Kind = arg case Op: e.Op = arg case string: e.Message = arg case error: e.Err = arg } } e.frame = errors.Caller(1) return e}
E
functionconst ( ENOTFOUND = Kind("notfound") EINVALID = Kind("invalid"))const ( DBDelOp Op = "db.delete" ServerDelOp Op = "server.delete")e := E(DBDelOp, ENOTFOUND, "this is error!") // Op, Kind, Messagee2 := E(ServerDelOp, e) // Op, error
IsKind
// Is reports that err.Kind and kind are equal or not.func IsKind(kind Kind, err error) bool { e, ok := err.(*Error) if ok { if e.Kind == kind { return true } } if err := errors.Unwrap(err); err != nil { return IsKind(kind, err) } return false}
if IsKind(e2, ENOTFOUND) == false { t.Fatalf("want: %v have: %v", true, false)}
BestError
// BestError returns a new error with latest Kind and Message of the chain of errorfunc BestError(err error, args ...interface{}) *Error { var e *Error if len(args) <= 0 { e = &Error{} } else { e = E(args...).(*Error) } if e.Kind == "" { if err := ErrorKind(err); err != nil { e.Kind = err.Kind } } if e.Message == "" { if err := ErrorMessage(err); err != nil { e.Message = err.Message } }
BestError
const notFound Kind = "notFound"const found Kind = "found"e1 := E(notFound, "message1")e2 := E(dbOp,e1)e3 := E(found,e2)e4 := E(serverOp, e3)bestErr := BestError(e4)if bestErr.Kind != found { t.Fatalf("kind: have: %v want: %v", bestErr.Kind, found)}if bestErr.Message != "message1" { t.Fatalf("message: have: %v want: %v", bestErr.Message, "message1")}json.Marshal(e4) // Output: { "op": "server", "kind" : "found", "message" : ...
Use wrapping error detail structs
*Error(Op, Kind(NotFound)) -> *PathError -> *Error(Op) -> io.EOF
var p *os.PathErrorif IsKind(err,NotFound) && errors.As(err,&p) { // handle this.}
Thank you
This slide is about the improvements to the error inspection and printing.
The error inspection & printing
doesn't change the language ifself (almost)
This package will be incorporated into the standard library's errors package in Go 1.13
Keyboard shortcuts
↑, ←, Pg Up, k | Go to previous slide |
↓, →, Pg Dn, Space, j | Go to next slide |
Home | Go to first slide |
End | Go to last slide |
Number + Return | Go to specific slide |
b / m / f | Toggle blackout / mirrored / fullscreen mode |
c | Clone slideshow |
p | Toggle presenter mode |
t | Restart the presentation timer |
?, h | Toggle this help |
Esc | Back to slideshow |