The error Interface in Go
What an error is in Go, how the built-in error interface works, and why Go returns errors as values instead of throwing exceptions.
Most languages you may have used before handle failure with exceptions. Something goes wrong deep inside a function, an exception is thrown, and it jumps up the call stack until a try/catch block catches it. Go does not work that way. In Go, a failure is just a value that a function hands back to you, the same way it hands back a number or a string.
That value has the type error. This page explains what error actually is, how a value becomes an error, and how to read the errors you get from the standard library.
What the error Type Is
error is a built-in interface with exactly one method:
type error interface {
Error() string
}
An interface is a list of methods. Any type that has all the listed methods automatically satisfies the interface. So any type with a method Error() string is an error. There is nothing else to it: no base class, no special keyword, no registration.
The Error() method returns a human-readable message. When you print an error with fmt.Println, Go calls Error() for you and prints the text it returns.
Here is the smallest program that gets an error from the standard library and prints it:
package main
import (
"fmt"
"strconv"
)
func main() {
n, err := strconv.Atoi("42x")
if err != nil {
fmt.Println("could not convert:", err)
return
}
fmt.Println("number:", n)
}
strconv.Atoi turns text into an int. It returns two values: the number and an error. The text "42x" is not a valid number, so the error is not nil, and the program prints:
could not convert: strconv.Atoi: parsing "42x": invalid syntax
Change "42x" to "42" and run it again. This time err is nil, the if block is skipped, and you see number: 42.
nil means no error:
An error value of nil means the call worked. Any non-nil value means something went wrong. Every error check in Go comes down to this one comparison, err != nil.
Why Go Uses Error Values Instead of Exceptions
Go's designers wanted failure to be visible in the code. When a function can fail, its signature says so, because error is one of its return types. When you call it, you see the err variable right there, and you decide what to do next on the very next line.
With exceptions, a function call that looks harmless can secretly jump to a handler far away. With error values, the control flow is ordinary: an if statement and a return. Nothing jumps.
The cost is that you write if err != nil often. Many people coming from Python, Java or JavaScript find this repetitive at first. The benefit is that you can read any Go function from top to bottom and see every place where it can fail and what it does about it.
Making Your Own Type Satisfy error
Because error is an ordinary interface, you can create your own error types. You only need a type with an Error() string method.
package main
import "fmt"
type TooColdError struct {
Celsius int
}
func (e TooColdError) Error() string {
return fmt.Sprintf("temperature %d C is below the safe limit", e.Celsius)
}
func checkTemp(c int) error {
if c < 5 {
return TooColdError{Celsius: c}
}
return nil
}
func main() {
if err := checkTemp(2); err != nil {
fmt.Println("warning:", err)
}
if err := checkTemp(20); err == nil {
fmt.Println("20 C is fine")
}
}
TooColdError is a normal struct. Giving it an Error() method is enough for checkTemp to return it as an error. Output:
warning: temperature 2 C is below the safe limit
20 C is fine
The struct also carries data, the actual temperature. Code that receives this error can later pull that number back out, which a plain text message cannot offer. The standard function errors.As is the tool for getting the struct back out of an error value.
Do not call Error() just to compare errors:
Beginners sometimes write if err.Error() == "file not found". This breaks as soon as the message wording changes or another layer adds context to it. Compare errors with errors.Is and errors.As instead. The message is for humans to read, not for code to match.
What an Error Message Looks Like
Standard library messages follow a pattern: lowercase, no final period, and pieces joined by : . For example:
open config.yaml: no such file or directory
strconv.Atoi: parsing "42x": invalid syntax
Each piece adds context, read from left to right as "what I was doing: what went wrong". When you write your own messages, follow the same style:
- start with a lowercase letter, because your message may end up in the middle of a longer one
- do not end with a period or a newline
- say what failed and with what input, for example
load user 17rather thanerror happened
The tool staticcheck reports capitalized error strings as ST1005, so most Go codebases follow this rule.
The error Interface in Function Signatures
When a function can fail, error is placed as the last return value. This is a convention, not a compiler rule, but every package you will use follows it.
func ReadFile(name string) ([]byte, error)
func Atoi(s string) (int, error)
func (c *Client) Do(req *Request) (*Response, error)
A function that has nothing else to return but can still fail returns only error:
func Remove(name string) error
func (f *File) Close() error
When the error is not nil, treat the other return values as unusable unless the function's documentation says otherwise. strconv.Atoi("42x") returns 0 for the number, and that 0 means nothing.
Using the result before checking the error:
Reading the result first and checking err later is a common cause of the runtime panic invalid memory address or nil pointer dereference. For example, resp, err := http.Get(url) followed by resp.Body.Close() crashes when the request failed, because resp is nil. Always check err first.
Summary
An error in Go is any value with an Error() string method, and nil means success. Functions report failure by returning an error as their last result, and callers check it with if err != nil right after the call. Because errors are plain values, you can build your own error types that carry useful data, and callers can react to the exact kind of failure instead of just printing a message.