Skip to content

Instantly share code, notes, and snippets.

@dpinela
Created January 24, 2018 11:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dpinela/f7fdeb0ce3e1f0b4f495917ad9210a85 to your computer and use it in GitHub Desktop.
Save dpinela/f7fdeb0ce3e1f0b4f495917ad9210a85 to your computer and use it in GitHub Desktop.
Go experience report about issues with variadic functions

Counter-intuitive behaviour of Go variadic functions

In Go, we can define functions that take a variable number of arguments, by defining a "rest" argument. When calling such a function, one can pass the elements of a slice as the "variadic" arguments with the ... syntax, as in:

elems := []string{"usr", "local", "bin"}
p := filepath.Join(elems...)

However, this only works if the slice:

  1. lines up exactly with the "rest" argument
  2. has exactly the same element type as that argument

I have seen both of these snags trip me up, as well as others. For example, regarding 1., I once tried to build a path with the following code:

return filepath.Join(h, ".mflg", elems...)

Due to the above limitation, I had to do this contraption instead, which is much less clear:

return filepath.Join(append([]string{h, ".mflg"}, elems...)...), nil

The second limitation, on the other hand, tends to come up when expanding a slice to pass to Printf. As an example, while doing one of the exercises in the Go tour, a colleague tried to print an IP address like this:

func (ip IPAddr) String() string {
	return fmt.Sprintf("%d.%d.%d.%d", ip[:]...)
}

This did not work, requiring them to expand the arguments by hand:

return fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])

This is, of course, because Sprintf's "rest" argument is effectively of type []interface{} and ip[:] of type []byte. Explaining this to my colleague turned out to be somewhat difficult; I had to talk about interface conversions and show them some examples of equivalent functions and calls with regular slice arguments to get them to understand why Go works this way. I don't think this (ostensibly) simple feature should be so complicated to explain.

In both cases, Go's current behaviour is logical, but unintuitive, especially when compared to other languages with similar features, like Python or Ruby. This can trip up both newcomers as well as more experienced users such as myself. In addition, the workarounds for both limitations make the code less concise and harder to read.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment