Go has no syntax for defining optional arguments in functions, so you have to use workarounds. I know 2:
- Pass a structure containing all optional arguments in the fields:
funcStructOpts(Opts{p1: 1, p2: 2, p8: 8, p9: 9, p10: 10})
- The method proposed by Rob Pike using functional arguments:
')
funcWithOpts(WithP1(1), WithP2(2), WithP8(8), WithP9(9), WithP10(10))
The second method basically does the same thing, but with syntactic sugar. The thought didn’t give me any peace, but how much does this sugar cost?
For tests, I used a structure with 10 options:
type Opts struct { p1, p2, p3, p4, p5, p6, p7, p8, p9, p10 int }
and 2 empty functions:
func funcStructOpts(o Opts) { }
func funcWithOpts(opts ...OptsFunc) { o := &Opts{} for _, opt := range opts { opt(o) } }
For those who have not worked with functional arguments I will tell you a little how they work. Each option is described as a function that returns a function that changes the structure with parameters, for example:
func WithP1(v int) OptsFunc { return func(opts *Opts) { opts.p1 = v } }
where
OptsFunc
is a
type OptsFunc func(*Opts)
When a function is called, they are passed as arguments, and inside the function in a loop, they fill in the structure with arguments:
o := &Opts{} for _, opt := range opts { opt(o) }
Here the magic ends, now we have a filled structure, it remains only to find out how much sugar costs. For this, I wrote a simple benchmark:
func BenchmarkStructOpts(b *testing.B) { for i := 0; i < bN; i++ { funcStructOpts(Opts{ p1: i, p2: i + 2, p3: i + 3, p4: i + 4, p5: i + 5, p6: i + 6, p7: i + 7, p8: i + 8, p9: i + 9, p10: i + 10, }) } } func BenchmarkWithOpts(b *testing.B) { for i := 0; i < bN; i++ { funcWithOpts(WithP1(i), WithP2(i+2), WithP3(i+3), WithP4(i+4), WithP5(i+5), WithP6(i+6), WithP7(i+7), WithP8(i+8), WithP9(i+9), WithP10(i+10)) } }
For testing, I used Go 1.9 on an Intel® Core (TM) i7-4700HQ CPU @ 2.40GHz.
Results:
BenchmarkStructOpts-8 100000000 10.7 ns/op 0 B/op 0 allocs/op
BenchmarkWithOpts-8 3000000 399 ns/op 240 B/op 11 allocs/op
The results are contradictory, on the one hand, the difference is almost 40 times, on the other - it is hundreds of nanoseconds.
It became interesting to me, and what time is spent on, the pprof output is lower:

Everything is logical, time is spent on allocating memory for anonymous functions, and as you know,
malloc
is time, a lot of time ...
For the purity of the experiment, I checked what happens when you call without arguments:
func BenchmarkEmptyStructOpts(b *testing.B) { for i := 0; i < bN; i++ { funcStructOpts(Opts{}) } } func BenchmarkEmptyWithOpts(b *testing.B) { for i := 0; i < bN; i++ { funcWithOpts() } }
Here the difference is slightly less, about 20 times:
BenchmarkEmptyStructOpts-8 1000000000 2.75 ns/op 0 B/op 0 allocs/op
BenchmarkEmptyWithOpts-8 30000000 57.0 ns/op 80 B/op 1 allocs/op
findings
For myself, I never decided what is best. I propose to talk a little in the comments, and to collect statistics a poll is below.