Overview

Appendix F. Self-referential options

This appendix compares two ways to configure a client: passing a single options struct versus using self-referential option functions. It frames the problem around ergonomics, clarity, and handling defaults, especially when zero values can be meaningful. The goal is to surface how each approach affects API usability, readability, and the ability to express intent unambiguously.

Passing a struct is straightforward and explicit: all settings live in one place and are easy to scan. However, it forces callers to pass an empty value or a helper for defaults, and it suffers from zero-value ambiguity (for example, whether 0 means “unset” or “turn off” for a retry count). Workarounds like adding auxiliary boolean flags increase complexity and clutter. These limitations motivate exploring an alternative that distinguishes “not provided” from “provided with zero.”

The self-referential option functions approach introduces a variadic API where each option is a function that returns a closure to mutate the options, with defaults applied first and user-supplied options layered on top. This makes defaults automatic when no options are given and removes ambiguity—e.g., explicitly passing a zero retry count clearly disables retries. In exchange, it requires more boilerplate, can make discoverability harder without documentation, and risks ambiguity or unintended overrides when the same option is passed multiple times or options interact. The appendix concludes that choosing between the struct and functional styles depends on context and preference, balancing simplicity and explicitness against flexibility and expressiveness.

FAQ

What problem does the self-referential options pattern solve compared to passing an Options struct?It avoids zero-value ambiguity (for example, distinguishing MaxRetries=0 from “unset”) and lets callers omit options entirely while still getting defaults, since options are applied via variadic functions over a default configuration.
How does zero-value ambiguity occur with struct options?When using a plain Options struct, a zero value like 0 for an int field can mean either “use the default” or “explicitly set zero.” For example, MaxRetries: 0 could mean “no retries” or “not set.”
How do option functions eliminate the zero-value ambiguity?Callers explicitly pass option functions such as MaxRetries(0). Since the function is provided, it unambiguously indicates the user wants MaxRetries set to 0, overriding the default.
Do I need to pass Defaults() to use defaults with SendNWith?No. SendNWith starts from defaults internally and applies any provided options. Calling SendNWith(...) with no options uses all defaults.
What is the Option type and how do I write an option function?Option is defined as type Option func(*Options). An option function (e.g., MaxRetries(n int) Option) returns a closure that sets the corresponding field on an *Options when applied.
How does SendNWith apply options internally?It creates a local options value from Defaults(), then loops over the provided Option functions and applies each to that value, finally passing the result to the request-sending logic.
How can I specify “no retries” explicitly?Use MaxRetries(0) with SendNWith. This explicitly sets retries to zero, unlike the struct approach where 0 could be ambiguous and might require an extra boolean like NoRetries.
Can I provide multiple options, and what if I pass the same option twice?Yes, you can pass multiple options like Concurrency(10), RPS(100). If you pass the same option multiple times, later options overwrite earlier ones, which can introduce ambiguity if done unintentionally.
What are the downsides of the option functions pattern?More boilerplate (each field needs a function), discoverability can be harder without docs, options can unintentionally override each other, dependencies between options can be subtle, and callers repeat the package name for each option.
When should I prefer struct options over option functions?Prefer a struct when you want simplicity, explicitness, and minimal boilerplate. Prefer option functions when you need clear defaults without forcing a placeholder struct and when you must distinguish zero values from “unset.” Ultimately, it’s case-by-case and a matter of preference.

pro $24.99 per month

  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose one free eBook per month to keep
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime

lite $19.99 per month

  • access to all Manning books, including MEAPs!

team

5, 10 or 20 seats+ for your team - learn more


choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Go by Example ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Go by Example ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Go by Example ebook for free