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 asMaxRetries(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 fromDefaults(), 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?
UseMaxRetries(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 likeConcurrency(10), RPS(100). If you pass the same option multiple times, later options overwrite earlier ones, which can introduce ambiguity if done unintentionally.
Go by Example ebook for free