'Difference between any/interface{} as constraint vs. type of argument?
As generics have been released in Go 1.18 pretty recently, I've started learning them. I generally get the concept, because I have some Java experience from the past. But I don't get some implementation specifics.
For instance: when it's more suitable to use any
instead of interface{}
? Here's an example:
func printInterface(foo interface{}) {
fmt.Printf("%v\n", foo)
}
func printAny[T any](foo T) {
fmt.Printf("%v\n", foo)
}
func (suite *TestSuite) TestString() {
printInterface("foo")
printAny("foo")
}
Both implementations work. However, if I try to print nil
with any
-version, I'll get a compile-time error:
cannot infer T.
https://go.dev/play/p/0gmU4rhhaOP
And I won't get this error if I try to print nil
with interface{}
-version.
So what's the use-case for any
? When and which benefits does it bring, compared to simply using interface{}
?
I'm asking to provide a specific example, where one implementation is objectively more suitable than another and/or where there is a specific benefit that can be evaluated.
Solution 1:[1]
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, sine both would be individually assignable toany
/interface{}
.
printInterface(12.5, 0.1) // ok
printInterface(12.5, "blah") // ok
printAny(10, 20) // ok, T inferred to int, 20 assignable to int
printAny(10, "k") // not ok, 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) // not ok, no way to infer T
printAny[any](nil, nil) // ok, T explicitly instantiated to any, nil assignable to any
Note: as explained in icza's answer, 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 assigned to variables of interface type.
Solution 2:[2]
any
is an alias for interface{}
. Spec: Interface types:
For convenience, the predeclared type
any
is an alias for the empty interface.
Since it is an alias, it doesn't matter which one you use. They are one and the same. They are interchangeable. You can replace one with the other, the code will mean the same.
any
is shorter and clearer, but only works from Go 1.18.
Since they are interchangeable, this also works:
func printInterface(foo any) {
fmt.Printf("%v\n", foo)
}
The reason why printAny()
doesn't work is due to it being a generic function with a type parameter. To use it, it must be instantiated (its type parameter must be assigned with a known type). Trying to call it with nil
carries no type information, so instantiation cannot happen, type inference won't work.
If you call it with a nil
value that carries type info, it'll work, or if you specify the type param explicitly (try it on the Go Playground):
printAny((*int)(nil))
printAny[*int](nil)
// Or
var r io.Reader
printAny(r)
And as said, any
is interchangeable with interface{}
, so you'll have the same code if you swap both occurrences (try this one on the Go Playground):
func printInterface(foo any) {
fmt.Printf("%v\n", foo)
}
func printAny[T interface{}](foo T) {
fmt.Printf("%v\n", foo)
}
Solution 3:[3]
Your issue is not related to the usage of any
/interface{}
— whose difference is purely cosmetic — but it is type inference. As you can see from this playground, if you instantiate your function with an explicit type, like printAny[any](nil)
it will work.
If you have a function with generic type you need to specify the types. However the go compiler is very smart and can infer some types for you. But nil
alone is impossible to infer.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | |
Solution 3 | blackgreen |