Beside any
and interface{}
being type aliases — hence, equivalent in usage —, there is a practical difference between any
as type parameter and any
as regular function argument, as in your example.
The difference is that in printAny[T any](foo T)
the type of foo
is not any
/interface{}
, but it’s T
. And T
after instantiation is a concrete type, that may or may not be an interface itself. You can then only pass arguments to an instantiated printAny
that can be assigned to that concrete type.
How this impacts your code is most evident with multiple arguments. If we change the function signatures a bit:
func printInterface(foo, bar any) {
fmt.Println(foo, bar)
}
func printAny[T any](foo, bar T) {
fmt.Println(foo, bar)
}
After instantiation:
- the function
printAny
accepts any two arguments of the same type — whichever is used to instantiateT
printInterface
, which is equivalent toprintInterface(foo, bar interface{})
can still accept two arguments of different types, since both would be individually assignable toany
/interface{}
.
printInterface(12.5, 0.1) // ok
printInterface(12.5, "blah") // ok, int and string individually assignable to any
printAny(10, 20) // ok, T inferred to int, 20 assignable to int
printAny(10, "k") // compiler error, T inferred to int, "k" not assignable to int
printAny[any](10, "k") // ok, T explicitly instantiated to any, int and string assignable to any
printAny(nil, nil) // compiler error, no way to infer T
printAny[any](nil, nil) // ok, T explicitly instantiated to any, nil assignable to any
A playground: https://go.dev/play/p/pDjP986cj96
Note: the generic version cannot be called with nil
without explicit type arguments simply because nil
alone doesn’t carry type information, so the compiler can’t infer T
. However nil
can be normally assigned to variables of interface type.