Is adding a variadic parameter to an existing Go function a breaking change?
For example:
// Old function
func Foo(a int)
// Updated to:
func Foo(a int, params ...string)
Callers of the API can omit the new parameter, so I would think the API is backwards-compatible.
Can anyone provide an example where a user of the old API could not use the new API without changing their code?
A variadic function is a function that accepts a variable number of arguments. In Golang, it is possible to pass a varying number of arguments of the same type as referenced in the function signature.
A function with a parameter that is preceded with a set of ellipses ( ... ) is considered a variadic function. The ellipsis means that the parameter provided can be zero, one, or more values. For the fmt. Println package, it is stating that the parameter a is variadic.
Variadic functions are functions (e.g. std::printf) which take a variable number of arguments. To declare a variadic function, an ellipsis appears after the list of parameters, e.g. int printf(const char* format...);, which may be preceded by an optional comma.
Calling them will continue to work without modification, but since the function signatures do not match, that may easily break some code.
For example (try it on the Go Playground):
func Foo(a int) {}
func Foo2(a int, params ...string) {}
func main() {
var f func(int)
f = Foo
f = Foo2 // Compile-time error!
_ = f
}
The line f = Foo2
produces a compile-time error:
cannot use Foo2 (type func(int, ...string)) as type func(int) in assignment
So this is a backward incompatible change, don't do it.
The above example gave a compile-time error, which is the lucky / better case, but there may also be code that would only fail at runtime (non-deterministic if / when that happens), like in this example:
func Foo(a int) {}
func Foo2(a int, params ...string) {}
func main() {
process(Foo)
process(Foo2) // This will panic at runtime (type assertion will not hold)!
}
func process(f interface{}) {
f.(func(int))(1)
}
Calling process(foo)
succeeds, calling process(foo2)
will panic at runtime. Try it on the Go Playground.
Your question was directed at functions, but the same "problem" exists with methods too (when used as method expressions or method values, for example see golang - pass method to function).
Additionally, this may break implicit interface implementations (it may make types not implement interfaces), like in this example (try it on the Go Playground):
type Fooer interface {
Foo(int)
}
type fooImpl int
func (fooImpl) Foo(a int) {}
type fooImpl2 int
func (fooImpl2) Foo(a int, params ...string) {}
func main() {
var f Fooer
f = fooImpl(0)
f = fooImpl2(0) // Compile time error!
_ = f
}
Because signatures don't match, fooImpl2
does not implement Fooer
, even though fooImpl
does:
cannot use fooImpl2(0) (type fooImpl2) as type Fooer in assignment: fooImpl2 does not implement Fooer (wrong type for Foo method) have Foo(int, ...string) want Foo(int)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With