Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Could adding variadic parameters to a function break existing code?

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?

like image 452
Duncan Jones Avatar asked Mar 14 '19 12:03

Duncan Jones


People also ask

What is go variadic function?

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.

What symbols are used in a function declaration to indicate that it is a variadic function?

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.

How do you declare a variadic function in C++?

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.


1 Answers

I. Changing functions

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.

II. Changing methods

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)
like image 120
icza Avatar answered Sep 18 '22 14:09

icza