This is not to say that using Options is wrong. Some things are highly customizable and having too many constructors would not be an appropriate solution. Worse would be a struct based config where you only set some fields some times. I just don't find myself creating these kinds of constructs. My most configurable things are usually server instances.
For those who do use the Options paradigm and find it useful, what are you creating?
The disconnect for OP here is that generalized systemic approach (think e.g. Java Beans for one of the earliest attempts) is necessary for the container-component architectures [but arguably un-necessary for application specific approaches (which has informed your experience.)]
Go, imho, is a poor fit for the container-component paradigm. It certainly can meet the initial requirements (context + options) but the lack of first class metadata facilities will limit the possibilities (sans code generation) in that architectural space.
[pre-emptive p.s.: "container" here /does not/ mean process level composition ala Docker, etc.]
> type Options struct { ... }
> func myMethod(arg1 string, options Options) {...}
Then have people pass in "Options{}" and let the zero-values imply default.
But most of the time, if you are making things complex, you're missing the whole point of golang. The innovation in golang is it's simplicity, if you miss that, then you might as well code C++ (which have more features for these kinds of things).
Rob doesn't explain why a config struct is not good for him in his reference article, and Dave is saying that's because 1/ it needs to be passed even when it's empty and 2/ its zero values may be a trouble, giving the example of explicitly setting `Port` to 0 to let OS selecting first free port available.
I would say that I have absolutely no problem with passing an empty option struct : that's simple. I know what is going on, I don't have to check sources for three methods to understand it.
Regarding the zero value problem, the example of port selection seems incredibly an edge case. Most of the time, if some option is numeric and can have a special feature, -1 will be used rather than 0 (like it's often the case when passing a limit to specify "no limit"). For the port problem, I would have no problem passing an `AutoselectFreePort bool` option.
But then again, maybe the pattern seems complicated to me because it's new. We'll see with time.
var price *float64
if price == nil {
// Handle empty value
}1/ a potentially big number of options, without creating functions with a huge list of parameters
2/ options with associated values (revision: int)
3/ reusable options between different operations, with type safety preventing wrong combination.
=> 1/ prevents you from using function overloading and default parameters
=> 2/ prevents you from using optionset (they're just bit masks)
and
=> 3/ prevents you from using regular sets or arrays as operation parameter.
I haven't spent more than half an hour trying to find a solution, so maybe there's a smart trick that would work, but it won't be an obvious solution either.
But the whole rationale for having 3/ in the first place isn't valid in Swift, so it seems to me that the obvious Swift solution is to use an enum per operation.
Use rust or Haskell if you want a powerful type system that can do fancy things.
golang is great, but you have to accept that some things are runtime errors, and that's fine, it keeps things simple. How often are options given conditionally anyways? Simple tests will cover this in most cases.
The upside with golang is that it's simple and easy to adopt; tiny learning curve, and code + APIs are intended to be simple.
If you are messing around with complex patterns in golang to get more compile-time correctness checks, you are most likely not keeping it simple. And thus, have missed the most important upside to golang.
srv = NewServer(addr).WithTLS(crt,key).WithRateLimit(30).WithPool(10).Start()
if srv.Err() {
// Handle
}Interfaces are generally a better solution.
The clean way to deal with errors in fluid APIs is to have a terminal .init() that returns an error (which can also specifically note which options are "illegal", etc.)
NewServer(Options{Addr: addr, RateLimit 30, Pool: 10})
IMO, keep it simple and remove the optional features if possible is also a good choice. If my golang API is complex, then I might aswell have done it in C++ :)Have you suggested this to the etcd maintainers?