Errors Are Values
Go has no exceptions. A function that can fail says so in its signature, by returning an error as its last result:
package main
import (
"fmt"
"os"
"strconv"
)
func main() {
n, err := strconv.Atoi("42x")
if err != nil {
fmt.Println("bad input:", err)
os.Exit(1)
}
fmt.Println(n * 2)
}
That n, err := ... / if err != nil pair is the most common shape in all of
Go. It looks repetitive at first; it's also the reason you can read any Go
function and see every path a failure can take. Nothing jumps over your
code the way a thrown exception does.
error is just an interface
There's no special machinery behind error — it's a one-method interface,
built into the language:
type error interface {
Error() string
}
Any type with an Error() string method is an error. This lesson's previous
ideas snap together here: errors are ordinary interface values, so they can
be returned, stored in slices, compared, wrapped — treated like any other
value. That's what Gophers mean by "errors are values."
Making errors
Two constructors cover most needs:
import (
"errors"
"fmt"
)
err1 := errors.New("connection refused")
err2 := fmt.Errorf("parsing line %d: value %q is not a number", 17, "42x")
errors.New makes a fixed-message error; fmt.Errorf formats one. Message
conventions: lowercase, no trailing punctuation, because messages get chained
together ("open config: read /etc/app.conf: permission denied").
nil means success
A function reports success by returning a nil error, and callers rely on
that contract absolutely:
func halve(n int) (int, error) {
if n%2 != 0 {
return 0, fmt.Errorf("cannot halve %d: it is odd", n)
}
return n / 2, nil
}
Note both sides of the convention: on failure, return the zero value plus
a non-nil error (don't make callers guess whether the result is usable); on
success, the error is exactly nil. Callers check err != nil and nothing
else — they don't inspect the message to decide if something failed.
Why no exceptions?
It's a deliberate trade. Exceptions create invisible exits: any call might
unwind your stack, and the compiler won't tell you which. Go chooses honest
control flow — a failure is data flowing back to the caller, handled with the
same if statements as any other data, and the one that decides what failure
means is the code with the context to decide. (Go does have panic for
truly unrecoverable bugs, but idiomatic APIs return errors.)
Try it
Write safeDivide, which returns an error instead of letting a division by
zero produce +Inf silently.
Practice
1. Safe Division
IntermediateIn Go, floating-point division by zero doesn't panic —
1.0 / 0.0quietly produces+Inf. Write a division function that refuses to do that, using the standard error convention instead:func safeDivide(a, b float64) (float64, error)- If
bis0, return0and a non-nil error whose message contains the phrasedivision by zero. - Otherwise return
a / band anilerror.
For example:
q, err := safeDivide(10, 4) // 2.5, nil _, err = safeDivide(1, 0) // 0, error saying "division by zero"Build the error with
errors.New("division by zero")or withfmt.Errorfif you'd like to include the operands in the message — the tests only require that the phrase appears. Remember the contract: exactly one of (useful value, nil error) or (zero value, non-nil error).package main import "fmt" // safeDivide returns a divided by b. When b is zero it returns 0 and an // error whose message contains "division by zero". func safeDivide(a, b float64) (float64, error) { // TODO: implement return 0, nil } func main() { q, err := safeDivide(10, 4) if err != nil { fmt.Println("error:", err) return } fmt.Println(q) }Sign in to write and run your solution in the browser.
Sign in for hints and the solution.
- If