gogenericsany

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

  • 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:

    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.