Error

Errors

fmt also expects the Error interface to be implemented on error types

Basic example

func (w *Wallet) Withdraw(amount Bitcoin) error {

	if amount > w.balance {
		return errors.New("overdrawn")
	}

	w.balance -= amount
	return nil
}

// test
t.Run("Withdraw too much", func(t *testing.T) {
	wallet := Wallet{}

	wallet.Deposit(1)
	err := wallet.Withdraw(3)

	if err == nil {
		t.Errorf("expected an error to occur")
	}
	
	if err.Error() != "overdrawn" {
		t.Errorf("wrong error message")
	}
})

Custom errors

To make an error type you just need to implement the error interface.

This pattern makes errors immutable and reusable.

type DictionaryErr string

const (
	ErrUnknownKey       = DictionaryErr("unknown key")
	ErrKeyAlreadyExists = DictionaryErr("key already exists")
)

func (e DictionaryErr) Error() string {
	return string(e)
}

type Dictionary map[string]string

func (d *Dictionary) Search(key string) (string, error) {
	def, ok := (*d)[key]

	if !ok {
		return "", ErrUnknownKey
	}

	return def, nil
}

Error formatter

func Racer(a, b string, timeout time.Duration) (winner string, error error) {
	select {
	case <-ping(a):
		return a, nil
	case <-ping(b):
		return b, nil
	case <-time.After(timeout):
		return "", fmt.Errorf("timed out waiting for %s and %s", a, b)
	}
}

Structured errors

type CustomError struct {
	customMessage string
}

func (e CustomError) Error() string {
	return fmt.Sprintf("well the bloody thing broke, %q", e.customMessage)
}

func ErrorProducer() error {
	return CustomError{customMessage: "it broke"}
}

// -------------------------------------
func ExampleErrorProducer() {
	got := ErrorProducer()

	if customErr, ok := errors.AsType[CustomError](got); ok {
		fmt.Println("match:", customErr.customMessage)
	}

	// Output: match: it broke
}