DMarlon Studio

Lesson 13 · 13 of 20

Errors as values · Go

Treat errors as values in Go: create them with errors.New and fmt.Errorf, check them with if err != nil and recognise them with errors.Is.

Go · Errors as values in DMarlon Studio: the example's code and its output in the terminal

Go has no exceptions: a function that can fail returns an error as its last result. withdraw returns the new balance and an error; when everything goes well, the error is nil, which means "no error".

errors.New("insufficient funds") creates an error with a message. Stored in the ErrNoFunds variable, it is a sentinel error: a known value you can compare against later. fmt.Errorf builds an error with formatting, just like Printf: invalid amount -5.00.

With %w, fmt.Errorf wraps another error: the message adds detail (withdraw 90.00: insufficient funds), but the original error is still inside. errors.Is(err, ErrNoFunds) looks for it, which is why it gives true for the 90 euro withdrawal and false for the negative amount.

The caller always checks: if err != nil. If there is an error, it prints it and, with continue, moves on to the next amount without touching the balance; otherwise it keeps newBalance. So 30 and then 45 euros come out of 100, leaving 25.00.

Code · main.go

package mainimport (	"errors"	"fmt")// errors.New creates an error value that can be recognised later.var ErrNoFunds = errors.New("insufficient funds")// The error goes last; nil means that everything went well.func withdraw(balance, amount float64) (float64, error) {	switch {	case amount <= 0:		return balance, fmt.Errorf("invalid amount %.2f", amount)	case amount > balance:		return balance, fmt.Errorf("withdraw %.2f: %w", amount, ErrNoFunds) // %w wraps it	}	return balance - amount, nil}func main() {	balance, amounts := 100.0, []float64{30, -5, 90, 45}	for i := 0; i < len(amounts); i++ {		newBalance, err := withdraw(balance, amounts[i])		if err != nil {			fmt.Println("Error:", err)			fmt.Println("  is ErrNoFunds?", errors.Is(err, ErrNoFunds))			continue		}		balance = newBalance		fmt.Printf("Took %.2f, balance %.2f\n", amounts[i], balance)	}}

Output · go run main.go

Took 30.00, balance 70.00
Error: invalid amount -5.00
  is ErrNoFunds? false
Error: withdraw 90.00: insufficient funds
  is ErrNoFunds? true
Took 45.00, balance 25.00

Key points

  • The error is the last result; nil means no error.
  • errors.New creates an error; fmt.Errorf a formatted one.
  • %w wraps one error inside another.
  • errors.Is recognises an error even when it is wrapped.
  • Always check with if err != nil.

Exercise

Add a daily limit: a sentinel error ErrDailyLimit that withdraw returns, wrapped with %w, when the amount is over 60 euros, and check for it in main with errors.Is.