The error Interface

Errors in Go are ordinary values: the error interface asks for one method, Error() string, and nil means nothing went wrong.

Syntaxtype error interface { Error() string }

An error in Go is an ordinary value. Nothing is thrown, and there is no separate channel for failures. A function that can fail returns one extra result of type error, and the caller looks at it.

One method

error is an interface. An interface is a named set of methods: any type that has those methods satisfies it, with no declaration saying so. The error interface asks for exactly one method, Error() string, which returns the message. That is the whole contract.

Making one

errors.New("disk full") returns a value whose Error method returns that text. It covers most cases. When the failure needs to carry data, such as a field name or an id, define your own type and give it an Error method. Any type can do this: a struct, a string type, even a slice type.

nil means no error

Returning nil for the error result means the call succeeded. nil is the zero value of every interface. It is the absence of a value, not an empty one.

Printing

fmt.Println(err) and fmt.Println(err.Error()) print the same text. The first is the shorthand: the fmt package checks whether the value has an Error method and calls it. Prefer the shorthand, because it prints <nil> when err is nil instead of panicking on a method call.

By convention error messages are lowercase with no trailing full stop, because callers put them inside longer sentences.

Example

Example · go
package main

import "fmt"

// TempError is our own error type. Any type with an Error() string method
// satisfies the error interface, with no declaration saying so.
type TempError struct {
	Celsius int
}

// Error is the single method the error interface requires.
func (e TempError) Error() string {
	return fmt.Sprintf("temperature %d is below freezing", e.Celsius)
}

// check reports a problem by returning a non-nil error. nil means all is well.
func check(c int) error {
	if c < 0 {
		return TempError{Celsius: c}
	}
	return nil
}

func main() {
	// fmt prints <nil> for an error that is nil.
	fmt.Println(check(20))

	err := check(-5)
	// These two lines print the same text. fmt calls Error() for you.
	fmt.Println(err)
	fmt.Println(err.Error())
}

// Output:
// <nil>
// temperature -5 is below freezing
// temperature -5 is below freezing

When to use it

  • Give your own function a way to report that its input was out of range.
  • Write a type whose error message includes the id or field that was wrong.
  • Decide whether a call succeeded by testing its error result against nil.

More examples

errors.New, and the two ways to read the message

Shows that err.Error() and printing err are the same thing, and that errors.New is enough when the message is a fixed string. Reach for errors.New whenever the caller only needs to know that something failed and what to tell the user.

Example · go
package main

import (
	"errors"
	"fmt"
)

func main() {
	// errors.New is the shortest way to get a value satisfying error.
	var err error = errors.New("disk full")

	// Longhand: call the method yourself.
	fmt.Println(err.Error())

	// Shorthand: fmt sees the Error method and calls it. Same output, and it
	// survives err being nil.
	fmt.Println(err)
}

// Output:
// disk full
// disk full

A slice type can be an error too

The error interface is satisfied by any type with the right method, not only by structs. Collecting several complaints into one error is a common use for this. The len check matters: returning an empty ValidationErrors would give the caller a non-nil error with no problems in it.

Example · go
package main

import (
	"fmt"
	"strings"
)

// ValidationErrors is a slice of messages that is itself an error.
type ValidationErrors []string

func (v ValidationErrors) Error() string {
	return "invalid: " + strings.Join(v, ", ")
}

func validate(name, email string) error {
	var problems ValidationErrors
	if name == "" {
		problems = append(problems, "name is empty")
	}
	if !strings.Contains(email, "@") {
		problems = append(problems, "email has no @")
	}
	if len(problems) == 0 {
		// Return a plain nil, not problems, so the caller sees no error.
		return nil
	}
	return problems
}

func main() {
	fmt.Println(validate("", "nope"))
	fmt.Println(validate("ada", "[email protected]"))
}

// Output:
// invalid: name is empty, email has no @
// <nil>

Errors are values, so they can be stored and passed around

Because an error is an ordinary value it can sit in a slice, be sent to another function, or be stored on a struct. This is what a batch import does: keep going, collect the failures, report them at the end. %v in Printf prints an error the same way Println does.

Example · go
package main

import (
	"errors"
	"fmt"
)

func main() {
	// A slice of errors, because an error is just a value.
	results := []error{
		nil,
		errors.New("row 2: bad date"),
		nil,
		errors.New("row 4: missing id"),
	}

	failed := 0
	for i, err := range results {
		if err != nil {
			failed++
			fmt.Printf("record %d: %v\n", i, err)
		}
	}
	fmt.Println("failures:", failed)
}

// Output:
// record 1: row 2: bad date
// record 3: row 4: missing id
// failures: 2

Discussion

  • Be the first to comment on this lesson.