Patterns#

A few idioms Go programs lean on heavily.

Errors as Values#

Functions return error as the last value; callers check it explicitly.

data, err := os.ReadFile("input")
if err != nil {
    return fmt.Errorf("read input: %w", err)
}

Defer Cleanup#

defer schedules a call to run when the surrounding function returns.

f, err := os.Open("data")
if err != nil {
    return err
}
defer f.Close()

Context Propagation#

Pass a context.Context as the first argument of any call that does I/O, spawns work, or might need to be canceled.

func Fetch(ctx context.Context, url string) (*Response, error) { ... }

Functional Options#

A common way to build configurable constructors.

type Server struct{ port int; tls bool }

type Option func(*Server)

func WithPort(p int) Option { return func(s *Server) { s.port = p } }
func WithTLS()       Option { return func(s *Server) { s.tls = true } }

func New(opts ...Option) *Server {
    s := &Server{port: 8080}
    for _, o := range opts {
        o(s)
    }
    return s
}

Small Interfaces#

Define interfaces in the package that uses them, and keep them small. The standard examples are one-method types like io.Reader and io.Writer.

Goroutine + Channel#

Don’t communicate by sharing memory; share memory by communicating. Use channels to coordinate goroutines, sync.WaitGroup to wait for a group to finish, and select to multiplex.

var wg sync.WaitGroup
for _, url := range urls {
    wg.Add(1)
    go func(u string) {
        defer wg.Done()
        fetch(u)
    }(url)
}
wg.Wait()