+ - 0:00:00
Notes for current slide
Notes for next slide

Go2 Error values

anarcher@gmail.com

2019-04-25

1 / 35

Intro

2 / 35

Background

Four ways to test errors

3 / 35

Background

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

4 / 35

Sentinel errors

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)

5 / 35

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.

6 / 35

Error-checking predicate function

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

7 / 35

Unnamed error values

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

8 / 35

Go2: Error Inspection

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

  • Wrapping errors (chain of errors) is a idea to provide additional information of error.
    • github.com/pkg/errors
    • IMHO,it's some kind of composition.

Formatting errors helps more readable information of errors for people

Stack Frame is for location information (file name, line number) of errors

9 / 35

Go2: Error Inspection

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
10 / 35

Inspecting errors

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.

11 / 35

Wrapper interface

type 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
12 / 35

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.

13 / 35

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.

14 / 35

As function

type Temporary interface {
Temporary() bool
}
fe := &FileError{err: FileNotFound} // has Temporary() method
e := &ConfigError{err: we}
var temp Temporary
if 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")
}
15 / 35

Opaque

// 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.

16 / 35

Printing errors

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
}
17 / 35

Formatting

func (e WriteError) FormatError(p xerrors.Printer) error {
p.Print(e.Error())
if p.Detail() {
e.frame.Format(p)
}
return e.Unwrap()
}
// go1.12
func (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)
18 / 35

Formatting

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.13
e := fmt.Errorf("more info: %w", err) // err is sql.ErrTxDone
if errors.Is(e,sql.ErrTxDone) { ...
  • Works FormatError and Unwrap methods if the last argument is an error err and the format string ends with : %s, : %v, or : %w
19 / 35

Stack Frame

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()
}
20 / 35

Who consumes our errors?

Programs

  • Programs use Code and Details to recover or do something else.
  • Can provide Code by SentinelError? (io.EOF)
  • Can provide Details by ErrorStruct? (os.PathError)

Users

  • For end users, Error should provide a human-readable message(Your request is not accepted)
  • interface { Error() string }

Operators

  • The operators need to the details of the system and works with this error.
  • They want to see as much information as possible.(Code,Details,Message,StackFrame,...)
*os.PathError -> *syscall.EPERM
21 / 35

Example: Simple Error System

It's inspired by Error handling in Upspin and uses go2 error value features.

22 / 35

Error type

// Error defines a application error
type 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
}
23 / 35

Error type

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.13
func (e *Error) Format(f fmt.State, c rune) { // implements fmt.Formatter
xerrors.FormatError(e, f, c) // will call e.FormatError
}
24 / 35

Error type

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()
}
25 / 35

Kind? Code?

httpcat https://http.cat/301

26 / 35

Kind? Code?

// Kind defines the kind of error
type Kind string // or uint8
  • https://godoc.org/upspin.io/errors#Kind
    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.
  • https://cloud.google.com/apis/design/errors
    • For example, instead of defining different kinds of "not found" errors, the server uses one standard google.rpc.Code.NOT_FOUND error code and tells the client which specific resource was not found.
    • Individual APIs should avoid defining additional error codes, since developers are very unlikely to write logic to handle a large number of error codes.
27 / 35

Operation

// Op describes an operation
type Op string
  • The Op field denotes the operation being performed.
  • For example: client.Lookup , dir/server.Glob
  • Logical trace point for operators (debugging).
const serverDelete errors.Op = "server/delete"
28 / 35

E function

func 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
}
29 / 35

E function

const (
ENOTFOUND = Kind("notfound")
EINVALID = Kind("invalid")
)
const (
DBDelOp Op = "db.delete"
ServerDelOp Op = "server.delete"
)
e := E(DBDelOp, ENOTFOUND, "this is error!") // Op, Kind, Message
e2 := E(ServerDelOp, e) // Op, error
30 / 35

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)
}
31 / 35

BestError

// BestError returns a new error with latest Kind and Message of the chain of error
func 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
}
}
32 / 35

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" : ...
33 / 35

Error details

Use wrapping error detail structs

*Error(Op, Kind(NotFound)) -> *PathError -> *Error(Op) -> io.EOF
var p *os.PathError
if IsKind(err,NotFound) && errors.As(err,&p) {
// handle this.
}
34 / 35

Thank you

35 / 35

Intro

2 / 35
Paused

Help

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