Hacker News new | ask | show | jobs
by lobster_johnson 3025 days ago
A big config struct is great, but it comes with a big downside: You have to rely on Go's zero value semantics to provide defaults. It's not always clear that 0 or an empty string means "no value".

It means that sometimes you'll have to use dummy values (-1 for "no value", for example) or pointers (nil means optional). It means that any boolean has to be phrased in such a way that false is the default (so if it feels natural to call the option UseKeychain and it should default to true, you have to invert it and call it DontUseKeychain).

Config structs also means it's easier for a caller to construct invalid configs:

  type Config struct {
    // Either stream or channel can be set, but not both
    OutputStream io.Writer
    OutputChan chan Event
  }
  // ...
  pipelines.New(pipelines.Config{
    OutputStream: myFile,
    OutputChan: myChan,
  })
The semantics cannot be guaranteed by the type system, so won't be caught until runtime. Neither will this, of course:

  // ...
  pipelines.New(
    pipelines.OutputStream(myFile),
    pipelines.OutputChan(myChan))
...but in this case we can quite sensibly mandate that the following option overrides the previous one, flipping the internal switch over to the setting you specify. With the Config struct, there's no way for pipelines.New() to know which one should have precedence.

(In this particular case, you can work around it by having a single Output field that uses an interface, but there are other situations where mutually incompatible fields can occur.)

1 comments

Yep! It was probably overstepping on my part to say that the entire benefit depends on that argument. I should instead have said that the tradeoff does not seem good to me.
It depends on the purpose. Config structs are indeed simpler and make tons of sense for cases where you control all the related code. So yeah, for code where you can do a mostly-automated refactor if you need to change the API, I totally agree that Config structs make more sense and functional options are huge overkill.

For stuff that needs finer-grained control, or long-lived API stability, functional options give you a lot more flexibility.