January 2018: functional errors

Meetup #45. Please RSVP at Meetup.

We get started at 6:30 pm.

Sponsors

Venue provided by Startup Edmonton.

Food sponsored by Bellstone Engineering.

Talks

Functional Options Pattern

Adam Wolfe Gordon explained the functional options pattern by first motivating it: a logging library needs many config options (destinations, level, prefix, date format) with sensible defaults, and each naive approach falls short. Multiple structs cover only one config dimension, a plain config struct leaves the zero value useless, a constructor with every parameter must change whenever a new option appears, and multiple constructors explode combinatorially. The pattern fixes this with a constructor that takes variadic option functions (func NewLogger(opts ...LoggerOpt) Logger), each a closure over the private implementation type that mutates the config; defaults are applied first and then each option. He showed it works for plain functions too (a Retry with exponential backoff) and noted real-world users like gRPC, Google Cloud, AWS, and etcd.

Key points:

  • Config-rich types need good defaults plus per-option overrides.
  • Naive approaches (many structs/constructors, exposed config structs) scale poorly.
  • Options are functions that take and mutate a private options type.
  • The constructor applies defaults, then runs each option in turn.
  • Callers can't create their own options because the argument type is unexported.
  • Works for constructors and ordinary functions (e.g. retry with backoff).
  • Used widely: gRPC, Google Cloud, AWS, etcd.

Adam Wolfe Gordon (slides)

To err is human

Nathan Youngman's talk "To err is human" was a tour of idiomatic Go error handling, starting from the observation that errors are normal and that if err != nil { return err }, far from being a nuisance, keeps the happy path unindented and makes code scannable. He dug into how errors work under the hood — the error interface, errors.New and its *errorString pointer, sentinel values like io.EOF and sql.ErrNoRows, and custom error types such as url.Error — and showed how multiple return values and defer simplify cleanup. The talk covered adding context with fmt.Errorf or pkg/errors, storing an error inside a struct so a whole operation can be written error-free, using recover only in rare cases, and passing errors over channels. He closed by simplifying if err != nil { return err }; return nil to just return fn() and by pointing to resources like "Errors are values."

Key points:

  • Errors are normal; if err != nil keeps the happy path readable.
  • The error interface is just Error() string; errors.New returns a pointer sentinel.
  • Multiple return values and defer make cleanup and propagation clean.
  • Add context with fmt.Errorf or pkg/errors rather than losing the cause.
  • Treat errors as values: store one in a struct and skip work after failure.
  • panic/recover are for rare cases; HTTP servers recover per request.
  • Errors can be passed between goroutines over channels.

Nathan Youngman (slides)

Jobs

  • Software Developer at Tendermint (Toronto)

News

Events