`any` 和 `interface{}` 作为约束条件与参数类型之间的区别是什么?

huangapple go评论86阅读模式
英文:

Difference between any/interface{} as constraint vs. type of argument?

问题

由于Go 1.18最近发布了泛型,我开始学习它们。我大致理解了概念,因为我之前有一些Java的经验。但是我不太明白一些具体的实现细节。

例如:在什么情况下使用any比使用interface{}更合适?这里有一个例子:

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")
}

这两种实现都可以工作。然而,如果我尝试使用any版本打印nil,我会得到一个编译时错误:

无法推断出 T。

https://go.dev/play/p/0gmU4rhhaOP

而如果我尝试使用interface{}版本打印nil,我不会得到这个错误。

那么any的用例是什么?相比于简单地使用interface{},它带来了什么好处?

我想要一个具体的例子,其中一个实现在客观上比另一个更合适和/或有一个具体的可以评估的好处。

英文:

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.

答案1

得分: 47

除了anyinterface{}是类型别名(因此在使用上等效)之外,在*any作为类型参数any作为常规函数参数*之间存在实际差异,就像你的示例中一样。

区别在于,在printAny[T any](foo T)中,foo的类型不是any/interface{},而是T。并且在实例化之后,T是一个具体类型,它本身可能是一个接口,也可能不是一个接口。因此,你只能传递给实例化的printAny那些可以赋值给该具体类型的参数。

这对你的代码的影响在于多个参数时最为明显。如果我们稍微改变函数签名:

func printInterface(foo, bar any) {
    fmt.Println(foo, bar)
}

func printAny[T any](foo, bar T) {
    fmt.Println(foo, bar)
}

在实例化之后:

  • 函数printAny接受相同类型的任意两个参数——无论用于实例化的是哪个类型
  • printInterface,它等效于printInterface(foo, bar interface{}),仍然可以接受两个不同类型的参数,因为两者都可以单独赋值给any/interface{}
printInterface(12.5, 0.1)    // 可行
printInterface(12.5, "blah") // 可行,int和string可以单独赋值给any

printAny(10, 20)             // 可行,T推断为int,20可以赋值给int
printAny(10, "k")            // 编译错误,T推断为int,"k"不能赋值给int
printAny[any](10, "k")       // 可行,T显式实例化为any,int和string可以赋值给any

printAny(nil, nil)           // 编译错误,无法推断T的方式
printAny[any](nil, nil)      // 可行,T显式实例化为any,nil可以赋值给any

一个示例:https://go.dev/play/p/pDjP986cj96

注意:通用版本不能仅使用nil调用,因为nil本身不携带类型信息,所以编译器无法推断T。但是,nil可以正常赋值给接口类型的变量。

英文:

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 instantiate T
  • printInterface, which is equivalent to printInterface(foo, bar interface{}) can still accept two arguments of different types, since both would be individually assignable to any/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.

答案2

得分: 27

anyinterface{}的别名。根据规范,any是空接口的别名。

由于它是一个别名,使用哪个都无所谓。它们是相同的,可以互换使用。你可以用其中一个替换另一个,代码的含义是相同的。

any更短更清晰,但只适用于Go 1.18及以上版本。

由于它们是可互换的,下面的代码也是有效的:

func printInterface(foo any) {
    fmt.Printf("%v\n", foo)
}

printAny()无法工作的原因是它是一个带有类型参数的泛型函数。要使用它,必须进行实例化(将其类型参数赋值为已知类型)。尝试使用nil调用它不会携带类型信息,因此无法进行实例化,类型推断也无法工作。

如果你使用携带类型信息的nil值调用它,它将起作用,或者如果你明确指定类型参数(在Go Playground上尝试一下):

printAny((*int)(nil))
printAny[*int](nil)
// 或者
var r io.Reader
printAny(r)

正如前面所说,anyinterface{}是可互换的,因此如果交换两个出现的位置,代码仍然有效(在Go Playground上尝试一下):

func printInterface(foo any) {
    fmt.Printf("%v\n", foo)
}

func printAny[T interface{}](foo T) {
    fmt.Printf("%v\n", foo)
}
英文:

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)
}

答案3

得分: 5

你的问题与any/interface{}的使用无关,它们之间的区别纯粹是表面上的。问题出在类型推断上。从这个 playground可以看出,如果你使用显式类型实例化函数,比如printAny[any](nil),它就能正常工作。

如果你有一个带有泛型类型的函数,你需要指定类型。不过,Go 编译器非常聪明,可以为你推断一些类型。但是单独的nil是无法推断的。

英文:

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.

huangapple
  • 本文由 发表于 2022年3月26日 20:29:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/71628061.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定