Error handling in Go
November 21, 2022
You can get far with Go just parrotting if err != nil, but reading these topics
will help you get byeond this.
Read: https://go.dev/blog/error-handling-and-go
Read: https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully
Watch: https://www.youtube.com/watch?v=lsBF58Q-DnY
The error type is an interface type that wraps any value that can describe itself as a string:
type error interface {
Error() string
}
The errors package contains an errorString type, which you can call New() to return errorString:
// errorString is a trivial implementation of error.
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
// New returns an error that formats as the given text.
func New(text string) error {
return &errorString{text}
}
The alternative to errors.New("my error") is fmt.Errorf("%s", "A printf like error").
In go 1.13 error wrapping was introduced into the std library.
The fmt verb %w was added to wrap errors, that is why there is
no specific errors.Wrapf() in the std lib
func main() {
if err := foo(); err != nil {
fmt.Println(err)
}
}
func foo() error {
_, err := os.Open("foo.txt")
if err != nil {
return fmt.Errorf("failed to open file: %w", err)
}
return nil
}
The advantage to using the %w is that you can unwrap the error with errors.Unwrap()
An aside: the relationship between fmt and errors package is a little confusing, digging into the fmt package
you can see that fmt.Errorf is a complex wrapper around errors.New() with special handling for the %w
verb
package fmt
import (
"errors"
"sort"
)
// Errorf formats according to a format specifier and returns the string as a
// value that satisfies error.
//
// If the format specifier includes a %w verb with an error operand,
// the returned error will implement an Unwrap method returning the operand.
// If there is more than one %w verb, the returned error will implement an
// Unwrap method returning a []error containing all the %w operands in the
// order they appear in the arguments.
// It is invalid to supply the %w verb with an operand that does not implement
// the error interface. The %w verb is otherwise a synonym for %v.
func Errorf(format string, a ...any) error {
p := newPrinter()
p.wrapErrs = true
p.doPrintf(format, a)
s := string(p.buf)
var err error
switch len(p.wrappedErrs) {
case 0:
err = errors.New(s)
case 1:
w := &wrapError{msg: s}
w.err, _ = a[p.wrappedErrs[0]].(error)
err = w
default:
if p.reordered {
sort.Ints(p.wrappedErrs)
}
var errs []error
for i, argNum := range p.wrappedErrs {
if i > 0 && p.wrappedErrs[i-1] == argNum {
continue
}
if e, ok := a[argNum].(error); ok {
errs = append(errs, e)
}
}
err = &wrapErrors{s, errs}
}
p.free()
return err
}
type wrapError struct {
msg string
err error
}
func (e *wrapError) Error() string {
return e.msg
}
func (e *wrapError) Unwrap() error {
return e.err
}
type wrapErrors struct {
msg string
errs []error
}
func (e *wrapErrors) Error() string {
return e.msg
}
func (e *wrapErrors) Unwrap() []error {
return e.errs
}
There’s some confusing history here. Good detail on this is provided in this Stack Overflow Post
tldr form the StackOverflow post:
- If you want stack traces in your errors, use github.com/pkg/errors.Errorf to wrap errors.
- If you don’t care about stack traces, use fmt.Errorf from the standard library.
- Never use errors.Wrapf any more. It’s for backward compatibility.
End aside…
We can use arbitrary data structures as error values for example from the json package
type SyntaxError struct {
msg string // description of error
Offset int64 // error occurred after reading Offset bytes
}
func (e *SyntaxError) Error() string { return e.msg }
...
if err := dec.Decode(&val); err != nil {
if serr, ok := err.(*json.SyntaxError); ok {
line, col := findLine(f, serr.Offset)
return fmt.Errorf("%s:%d:%d: %v", f.Name(), line, col, err)
}
return err
}
SyntaxError implements Error() string. If Decode() returns and err we can do a type
assertion and see if its of type json.SyntaxError, if it is then we can do special behavoirs
to print out the error.
Dave Chaney recommends against using Type assertions to decode your errors because it couples your code to the error type provided by the other package. He recommends wrapping errors instead.