
Reusable and type-safe options for Go APIs - derekchiang
https://derekchiang.com/posts/reusable-and-type-safe-options-for-go-apis/
======
sethammons
I find I don't care for implementing the Options paradigm. More often then
not, the things I've implemented have required parameters, and sometimes many
of them. Mixing required and options often feels kludgy. I feel the Options
method requires lots of documentation referencing, as opposed to more
discoverable (via your editor) constructors. I tend to use explicit parameters
or a struct with validators where every field is expected to be explicitly
set.

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?

~~~
chimeracoder
I have to say I agree. I came to Go from functional languages (primarily
Lisp), so you'd think that the "Functional Options for Friendly APIs" pattern
would appeal to me, but I've never seem an implementation that I've actually
liked to use.

~~~
hellcow
This is a nice implementation from Dave Cheney:
[https://github.com/pkg/profile](https://github.com/pkg/profile)

------
sfifs
I've been using Go as my preferred language for almost a decade now. Hackish
"clever" solutions like these coming from designers of the language like Rob
Pike and Dace Cheney simply indicate that the language is hitting its limits
of expressivity and needs to start thinking hard about overloading which is
the right solution to problems like this.

~~~
oelmekki
Rob Pike and Dave Cheney know way better than me, but I can't help to think
they're trying to be "easy rather than simple", here.

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.

~~~
tomjakubowski
The zero value problem would be a non-issue if Go had a built-in ?T type
("option T").

~~~
ctz
That's problematic, thanks to golang choosing to have always-nullable
reference types. "option T*" would therefore be a tristate type, unless it was
somehow specialised into representing nullability (obviously a major breaking
change).

~~~
weberc2
If mostly everyone uses Option instead of `nil`, the problem approaches a
vanishing point. Not perfect, but still a massive improvement. Of course,
generics won't be added until Go 2 anyway, at which point the community is
free to make a breaking change, though I doubt they'll ditch `nil`.

------
jopsen
Once you accept that the golang type system can't enforce everything your life
will be easier.

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.

~~~
tapirl
No languages are perfect. Yes Go type system can't enforce everything, but it
enforce many things. And Go is still language in evolving.

~~~
jopsen
My point was that complex type systems also have down sides. Even if you
strongly favor algebratic type systems, you have to admit they are harder to
learn (at-least initially).

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.

------
robmccoll
How is it not cleaner and simpler to just use something of a builder pattern?

    
    
       srv = NewServer(addr).WithTLS(crt,key).WithRateLimit(30).WithPool(10).Start()
       if srv.Err() {
         // Handle
       }

~~~
everdev
In Go, chaining is not idiomatic. For one, it becomes more challenging to
determine where the error occurred if you want to try to handle the error
instead of failing. Also, it requires each function in the chain to detect if
an error has already occurred.

Interfaces are generally a better solution.

~~~
eternalban
Clearly a fluid API can not return a 2+ tuple as its results so the error
issue you note is a non-issue.

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.)

------
tdrd
The downside to this solution is that the concrete option types are exported.
I believe it's possible to return unexported types from exported functions,
which would solve this, but that golint complains about that pattern.

Have you suggested this to the etcd maintainers?

~~~
derekchiang
That's a good point, and I've since updated the blog post and the code to use
anonymous interfaces instead.

