go handling errors - ghdrako/doc_snipets GitHub Wiki

Go has a built-in error type called error. error is based on the interface type, with the following definition:

  type error interface {
  Error() string
}

Creating an error

err := errors.New("this is an error")
err := fmt.Errorf("user %s had an error: %s", user, msg)

Using an error

Go's multiple return ability to return two values: the result and the error.

func Divide(num int, div int) (int, error) {
  if div == 0 {
    // We return the zero value of int (0) and an error.
    return 0, errors.New("cannot divide by 0")
  }
  return num / div, nil
}
func main() {
  divideBy := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
  for _, div := range divideBy {
    res, err := Divide(100, div)
    if err != nil {
      fmt.Printf("100 by %d error: %s\n", div, err)
      continue
    }
    fmt.Printf("100 divided by %d = %d\n", div, res)
  }
}

divide our numbers and check the returned error type to see if it is not nil. If it is, we know we had an error and should ignore the return value. If not, we know the operation completed successfully.

Creating named errors

creating specific types of errors using the var keyword and errors.New() or fmt.Errorf()

var (
  ErrNetwork = errors.New("network error")
  ErrInput = errors.New("input error")
)

We can use the errors package's Is() function to detect the error type and retry on ErrNetwork and not on other errors, as follows:

// The loop is for retrying if we have an ErrNetwork.
for {
  err := someFunc("data")
  if err == nil {
  // Success so exit the loop
    break
  }
  if errors.Is(err, ErrNetwork) {
    log.Println("recoverable network error")
    time.Sleep(1 * time.Second)
    continue
  }
  log.Println("unrecoverable error")
  break // exit loop, as retrying is useless
}

Custom errors

error type is simply an interface, you can implement your own custom errors.

const (
  UnknownCode = 0
  UnreachableCode = 1
  AuthFailureCode = 2
)
type ErrNetwork struct {
  Code int
  Msg string
}
func (e ErrNetwork) Error() string {
  return fmt.Sprintf("network error(%d): %s", e.Code, e.msg)
}

We can now return a custom network error for something such as an authentication

failure, as follows:
return ErrNetwork{
Code: AuthFailureCode,
Msg: "user unrecognized",
}

When we receive an error from a call, we can detect if it was a network error using the errors.As() function, as follows:

var netErr ErrNetwork
if errors.As(err, &netErr) {
if netErr.Code == AuthFailureCode {
log.Println("unrecoverable auth failure: ", err)
break
}
log.Println("recoverable error: %s", netErr)
}
log.Println("unrecoverable error: %s", err)
break

Wrapping errors

We do this using fmt.Errorf() with %w for variable substitution of our error type. Let's say we want to call someFunc() from another function called restCall() and add more information, as illustrated in the following code snippet:

func restCall(data) error {
  if err := someFunc(data); err != nil {
    return fmt.Errorf("restCall(%s) had an error: %w",data, err)
  }
  return nil
}

Someone using restCall() can detect and extract an ErrNetwork using errors.As(), just as we did before. The following code snippet provides an illustration of this:

for {
  if err := restCall(data); err != nil {
    var netErr ErrNetwork
    if errors.As(err, &netErr) {
      log.Println("network error: ", err)
      time.Sleep(1 * time.Second)
      continue
    }
    log.Println("unrecoverable: ", err)
  }
}