Part 1 · 2 chapters · ~12 min

The Type System

Static types and inference, structs and embedding (composition, not inheritance), methods and pointer versus value receivers, interfaces satisfied implicitly, the empty interface and type switches, errors as values with wrapping, generics with constraints, and the nil interface trap.

3

Interfaces, methods and embedding

code
type Notifier interface { Send(ctx context.Context, to, msg string) error }

type SMSClient struct{ apiKey string }
func (c *SMSClient) Send(ctx context.Context, to, msg string) error { /* call the provider */ return nil }  // pointer receiver

type Audited struct{ Notifier }       // embedding: Audited gets Send automatically (composition)
func (a Audited) Send(ctx context.Context, to, msg string) error {
	log.Printf("notify %s", to); return a.Notifier.Send(ctx, to, msg)
}

// generics with a constraint
type Number interface{ ~int64 | ~float64 }
func Sum[T Number](xs []T) T { var s T; for _, x := range xs { s += x }; return s }
INTERFACES ARE SATISFIED IMPLICITLY
no "implements" keyword: having the methods is enough
interface NotifierSend(ctx, msg) errortype SMSClienthas Sendtype EmailClienthas Sendtype FakeNotifierhas Send (tests)func Alert(n Notifier, ...)accepts any of them
swipe the figure sideways, or tap expand for full screen
1/5
the interface
An interface lists method signatures. Any type with those methods satisfies it, without declaring so.
a set of methodsno implements keyword
4

Errors as values

code
var ErrInsufficientFunds = errors.New("insufficient funds")          // a sentinel error

func Debit(a *Account, amt int64) error {
	if a.Balance < amt { return fmt.Errorf("debit %s: %w", a.ID, ErrInsufficientFunds) }   // wrap with %w
	a.Balance -= amt; return nil
}
if err := Debit(acct, 900_000); errors.Is(err, ErrInsufficientFunds) { /* 422 */ }

// custom error types carry data; errors.As extracts them
type LimitError struct{ Limit int64 }
func (e *LimitError) Error() string { return fmt.Sprintf("over daily limit %d", e.Limit) }
var le *LimitError; if errors.As(err, &le) { /* use le.Limit */ }
the nil interface trap
An interface holding a nil pointer is not nil: var p *LimitError = nil; var err error = p; err != nil is true. Return a literal nil for "no error", never a typed nil pointer.