This is an idiom for quickly building golang APIs that consume optional parameters.
RPC clients typically have functions that consume both required and optional parameters. For example a function doing a search will take a required search query and optional limit & optional sort direction. If you were to write this as a Go function it would be:
func Search(query string, limit int, sort string) {
... process `query`, `limit`, and `sort` ...
}
So, if the caller doesn’t supply values for limit
and sort
you’d like these default to 20
and "ASC"
, respectievly, so you’d like to write the following, but you can’t:
func Search(query string, limit int = 20, sort string = "ASC") {
... process `query`, `limit`, and `sort` ...
}
Even without this syntax, I think the best you can do is to note that the parameters are optional by name, and pass go-defaults, e.g.
func Search(query string, optLimit int, optSort string) {
limit := 20
if optLimit != 0 {
limit = optLimit
}
sort := "ASC"
if optSort != "" {
sort = optSort
}
... process `query`, `limit`, and `sort` ...
}
So calls to Search
would look like:
Search("some query", 0, "")
Search("some query", 10, "")
Search("some query", 0, "DESC")
Search("some query", 100, "DESC")
This still has the property that when you evolve Search
to have another optional parameter, say verbose
or debug
, you need to change all the callers and the signature gets longer. The calls from above would now look like:
Search("some query", 0, "", false, false)
Search("some query", 10, "", false, false)
Search("some query", 0, "DESC", false, false)
Search("some query", 100, "DESC", false, false)
This can be mitigated with refactoring tools, but it’s still annoying.
Instead you can use the following pattern with genopts to generate wrappers and (optionally) goutil/or to incorporate the defult values.
//go:generate --function Search "limit:int" "sort:string"
func Search(query string, optss...SearchOption) {
opts := MakeSearchOptions(optss...)
limit := or.Int(opts.Limit(), 20)
sort := or.String(opts.Sort(), "ASC")
... process `query`, `limit`, and `sort` ...
}
then calls to Search
look like this and don’t require boilerplate defaults:
Search("some query")
Search("some query", SearchLimit(10))
Search("some query", SearchSort("DESC"))
Search("some query", SearchLimit(100), SearchSort("DESC"))
and when you add debug
and verbose
, they would be unchanged. You would just expand the function a little to handle the new optional params:
//go:generate --function Search "limit:int" "sort:string" debug verbose
func Search(query string, optss...SearchOption) {
opts := MakeSearchOptions(optss...)
limit := or.Int(opts.Limit(), 20)
sort := or.String(opts.Sort(), "ASC")
debug := opts.Debug()
verbose := opts.Verbose()
... process `query`, `limit`, `sort`, `debug`, and `verbose` ...
}
You can see some real examples.