An error is a value
What you'll learn:
- explain why an error in Go is an ordinary value, and read signatures like
(int, error); - create errors with
errors.Newandfmt.Errorf; - check an error right after the call with
if err != nil; - wrap an error with
%wand find it witherrors.Is.

Imagine you ordered a parcel. The courier hands you the box together with a delivery note. Usually the note is clean, but sometimes it carries a remark: "item damaged" or "wrong size". It makes sense to read the note first and only then open the box.
Go functions that may fail at their job work the same way. Not every string can be turned into a number or a bool. The strconv.ParseBool function from the strconv package understands "true" and "false", but it cannot parse the word "yes". It reports the failure through its second result—that is the delivery note:
package main
import (
"fmt"
"strconv"
)
func main() {
b, err := strconv.ParseBool("yes")
fmt.Println(b, err) // false strconv.ParseBool: parsing "yes": invalid syntax
b, err = strconv.ParseBool("true")
fmt.Println(b, err) // true <nil>
}Let's walk through the example line by line:
- The first call gets
"yes"and returnsfalsetogether with an error. - The first
Printlnprints both results. The error's text names the function, the string and the reason:invalid syntax. - The second call parses
"true"and returnstrue. - The second
Printlnprintstrue <nil>: the second result is<nil>—a clean note.
The program neither stops nor crashes. The error arrives in the variable err as an ordinary value, and the program decides what to do with it. When parsing succeeds, err holds nil.
A signature with an error
That the function can fail is visible from its signature alone. A signature is the line with the function's name, its parameters and its result types. For strconv.ParseBool it is func ParseBool(str string) (bool, error). Let's take it apart:
ParseBool—the function's name;(str string)—the parameter: the string to parse;(bool, error)—two results: the value itself and the error.
The second result of type error warns the calling code in advance, before the first run. The first result cannot be taken alone: a call with one variable on the left does not compile.
The error interface
The error type is built into the language, like int. Here is what you need to know about it:
- it is an interface with one method:
Error() string; - the method returns the error's text, and that is what
fmt.Printlnprints; - any type with such a method can be a value of type
error.
Here is an error type of your own and a function that returns it:
type tooOld int
func (a tooOld) Error() string {
return fmt.Sprintf("age %d is too big", int(a))
}
func checkAge(n int) (int, error) {
if n > 150 {
return 0, tooOld(n)
}
return n, nil
}The checkAge function returns the value tooOld(n) where an error is expected. The compiler accepts it: tooOld has the method Error() string, so the type implements the interface. The standard library's errors are built the same way: each has a type with an Error() method.
nil means success
The zero value of an interface is nil. For an error it means "everything went fine", so checkAge returns n, nil for a valid age:
age, err := checkAge(30)
fmt.Println(age, err) // 30 <nil>
age, err = checkAge(200)
fmt.Println(age, err) // 0 age 200 is too bigLet's go through both calls:
checkAge(30)passes the check and prints30 <nil>.checkAge(200)returns0and an error, andfmt.Printlnprints its text:age 200 is too big.
Two rules for results
Functions that return an error follow two rules:
- The error comes as the last result.
- If it is not
nil, the other results are not relied on: they usually hold the zero values of their types.
That is why checkAge returns 0 on failure, not the age itself.
Here the parcel picture stops working. In real life you can sometimes take something intact out of a dented box. In Go, when there is an error, the contents of the box stay untouched: the note says they can't be trusted.
Comparing with nil
The value err is compared with nil the same way as a pointer:
err == nilis success;err != nilis failure.
Reading the error's text is not needed for that. On success, return exactly nil.
This is a classic interview question: if you return an empty pointer of your own error type as an error, the calling code gets err != nil. As you already know from the interfaces theme, an interface holding a nil pointer is not equal to nil.
Common mistake
On failure, nil is returned in place of the number:
func checkAge(n int) (int, error) {
if n > 150 {
return nil, tooOld(n) // ✗
}
return n, nil
}cannot use nil as int value in return statementA pointer, a slice, a map or an interface can be nil, but a number cannot. For a result of type int, return its zero value: return 0, tooOld(n).
Key points
- An error in Go is an ordinary value of type
error; it comes as the last result. erroris an interface with one method,Error() string, and any type with that method implements it.err == nilmeans success; whenerr != nil, the other results are not relied on.- On success return
nil; on failure return the zero values of the other results, such as0.
Subscribe to take the course
The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.