error vs errors

Handling errors is a crucial part of writing robust programs. When scanning the Go packages, it is not rare to see APIs which have multiple return values with an error among them. For example:

func Open(name string) (*File, error)

Open opens the named file for reading. If successful, methods on the returned file can be used for reading; the associated file descriptor has mode O_RDONLY. If there is an error, it will be of type *PathError.

And the idiomatic method of using os.Open function is like this:

  1. file, err := os.Open("file.go") // For read access.
  2. if err != nil {
  3. log.Fatal(err)
  4. }
  5. defer file.Close()

So to implement resilient Go programs, how to generate and deal with errors is a required course.

Go provides both error and errors, and you shouldn’t mix up them. error is a built-in interface type:

  1. type error interface {
  2. Error() string
  3. }

So for any type, as long as it implements Error() string method, it will satisfy error interface automatically. errors is one of my favorite packages since it is very simple (The life will definitely be easier if every package is similar to errors!). Removing the comments, the amount of core code lines is very small:

  1. package errors
  2. func New(text string) error {
  3. return &errorString{text}
  4. }
  5. type errorString struct {
  6. s string
  7. }
  8. func (e *errorString) Error() string {
  9. return e.s
  10. }

The New function in errors package returns an errorString struct which complies with error interface. Check the following example:

  1. package main
  2. import (
  3. "errors"
  4. "fmt"
  5. )
  6. func maxElem(s []int) (int, error) {
  7. if len(s) == 0 {
  8. return 0, errors.New("The slice must be non-empty!")
  9. }
  10. max := s[0]
  11. for _, v := range s[1:] {
  12. if v > max {
  13. max = v
  14. }
  15. }
  16. return max, nil
  17. }
  18. func main() {
  19. s := []int{}
  20. _, err := maxElem(s)
  21. if err != nil {
  22. fmt.Println(err)
  23. }
  24. }

The execution result is here:

  1. The slice must be non-empty!

In real life, you may prefer to use Errorf function defined in fmt package to create error interface, rather than use errors.New() directly:

func Errorf(format string, a …interface{}) error

Errorf formats according to a format specifier and returns the string as a value that satisfies error.

So the above code can be refactored as follows:

  1. func maxElem(s []int) (int, error) {
  2. ......
  3. if len(s) == 0 {
  4. return 0, fmt.Errorf("The slice must be non-empty!")
  5. }
  6. ......
  7. }

References:
The Go Programming Language.