Home > Software engineering >  Go generics: infer type parameter for variadic function with zero arguments
Go generics: infer type parameter for variadic function with zero arguments

Time:11-06

Let's say I have a function with two generic parameters, one of them variadic:

func Constructor[F any, Opt any](f F, opts ...Opt) {}

Calling this function works fine if I pass in a few options:

Constructor(func() *myService { return ... }, 1, 2, 3)

However, calling it without any Opts fails:

Construtor(func() *myService { return ... })

The compiler complains:

Cannot use 'func() *myService' (type func() *myService) as the type (F, Opt) or F

I assume that’s because the compiler can’t figure out the type of Opt in this case.

While this makes sense, it’s annoying nevertheless. The compiler doesn't need the type of Opt, since it's empty.

One way to work around this is to define two functions, Constructor and ConstructorWithOpts. It would be really nice to just have a single function though. Any ideas?

CodePudding user response:

The compiler doesn't need the type of Opt, since it's empty.

Even if the caller decides to supply zero arguments, the body of the function can still operate with the value of the variadic parameter. So the compiler definitely needs the type of Opt.

Furthermore the rules for type inference are clear:

Type inference is based on

  • a type parameter list
  • a substitution map M initialized with the known type arguments, if any
  • a (possibly empty) list of ordinary function arguments (in case of a function call only)

When you have zero arguments for a certain type parameter, the third option as of above isn't applicable. If F and Opt are completely unrelated, the second option is also not applicable.

As a corollary, consider that the caller must supply all the type args if they ever need to declare a value of your Constructor function type:

ctor := Constructor[func(), any] // not called!
ctor(f, 1, 2, 3)

The cleanest is obviously to let the compiler infer F and specify Opts explicitly, although this requires inverting the order of the type parameters in the function declaration, so that clients can supply the first and omit the second. You may define an any type to improve readability:

func Constructor[Opt any, F any](f F, opts ...Opt) {}

type NoOpts any // just for better readability

func main() {
    f := func() *myService { return ... }

    // NoOpts instantiates Opt; F inferred
    Constructor[NoOpts](f)
}

Otherwise just make Constructor non-variadic:

func Constructor[F any](f F) {
    ConstructorWithOpts[F, any](f)
}

func ConstructorWithOpts[F any, Opt any](f F, opts ...Opt) {
    // ...
}

CodePudding user response:

You may provide an empty slice for the variadic argument:

Constructor(func() *myService { return nil }, []int{}...)

You can also pass nil, but it's more verbose:

Constructor(func() *myService { return nil }, ([]int)(nil)...)

Or provide values for the type parameters:

Constructor[func() *myService, any](func() *myService { return nil })

Try these on the Go Playground.

  • Related