Skip to content

Latest commit

 

History

History
153 lines (102 loc) · 4.42 KB

08-Errors.md

File metadata and controls

153 lines (102 loc) · 4.42 KB

Error Handling

Go has no exceptions, we say errors are values. Errors are not exceptional, rather part of the control flow.

type error interface {
    Error() string
}

Another example of a small interface.

Motto: Don't just check errors, handle them gracefully.

Note: A common complaint is error handling in go and that you will write:

if err := ...; err != nil {
    // handle error
}

over and over again. That's true. If you see an unhandled error or an _ where the error should be, then consider it a potential issue.

Side note: An empirical study suggested that exceptions handling is not necessarily fine grained, or that exceptions are not handled at all (Greg Wilson, Software Engineering's Greatest Hits).

Ad-Hoc messages

Use fmt.Errorf to returns ad-hoc error messages.

Example:

Predefine errors

Use errors.New to declare errors in a package.

Advantage:

  • You can check for specific errors at different call sites

The implementation of errors.New is simple:

// New returns an error that formats as the given text.
// Each call to New returns a distinct error value even if the text is identical.
func New(text string) error {
	return &errorString{text}
}

// errorString is a trivial implementation of error.
type errorString struct {
	s string
}

func (e *errorString) Error() string {
	return e.s
}

Example for declared errors:

Return or compare errors:

A custom error type

The fs.PathError is an example.

// PathError records an error and the operation and file path that caused it.
type PathError struct {
	Op   string
	Path string
	Err  error
}

func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }

// more methods ...

Other examples:

Wrapping errors, adding context

When something fails, we may want to just pass the error or add more context to it.

You could define a custom type, like:

type ErrorStack struct {
    errs []error
}

func (s *ErrorStack) Error() string { ... }
func (s *ErrorStack) Push(err error) { ... }

We could also add methods to check, whether this stack contains specific errors. Two other options, which are a bit more preferred:

Examples:

Additional Helpers

  • errors.Is (compare values)
  • errors.As (compare type)
if errors.Is(err, ErrPermission) {
    // err, or some error that it wraps, is a permission problem
}

Before Go 1.13, we would have to write:

if e, ok := err.(*QueryError); ok && e.Err == ErrPermission {
    // query failed because of a permission problem
}

Example for errors.As:

var e *QueryError
// Note: *QueryError is the type of the error.
if errors.As(err, &e) {
    // err is a *QueryError, and e is set to the error's value
}