任何/接口{}作为约束与参数类型的区别是什么?

45

由于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{} 相比,它带来了哪些好处?
我想提供一个具体的示例,在这个示例中,一个实现明显比另一个实现更合适和/或有一个可以评估的具体优势。

1
这可能会有所帮助:https://go.dev/play/p/2abNLSPxw_v - Tiago Peczenyj
5
值得指出的是,并非所有可以使用泛型实现的内容都必须使用泛型实现。仅因为 Go 现在具有了泛型,这并不意味着每个函数都应该是泛型的。不要像许多 Go 初学者一样犯同样的错误,滥用通道和 goroutine,即使在不需要的地方也要乱搞。这也适用于您的例子,其中两个函数唯一共同之处是将参数传递给 fmt.Printf,而 fmt.Printf 本身不是泛型的。 - mkopriva
3个回答

57
除了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函数接受相同类型的任何两个参数,无论使用哪个来实例化T
  • printInterfaceprintInterface(foo, bar interface{})等效,因为两者都可以单独分配给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

一个游乐场:https://go.dev/play/p/pDjP986cj96

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


29

anyinterface{}aliasSpec: Interface types:

为了方便起见,预定义类型any是空接口的别名。

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

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

由于它们是可互换的,这也是有效的:

func printInterface(foo any) {
    fmt.Printf("%v\n", foo)
}
< p > printAny() 无法工作的原因是它是一个带有类型参数的通用函数。要使用它,必须实例化(必须将其类型参数分配为已知类型)。尝试使用nil调用它不会携带任何类型信息,因此无法进行实例化,也无法进行类型推断。

如果您使用带有类型信息的nil值调用它,则会正常工作,或者如果您明确指定类型参数(请在Go Playground上尝试):

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

正如所说,any 可以与 interface{} 互换使用,如果你将这两个词都替换掉,那么代码将保持不变(在 Go Playground 上尝试一下):

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

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

2
这并不完全解释了为什么它无法编译:https://go.dev/play/p/0gmU4rhhaOP - colm.anseo
1
但是说它们在Go 1.18+中是可以互换的并不完全正确,因为我问题中的示例恰好展示了它们不能互换的情况,即any相对于interface{}有一定的限制... - Dmytro Titov
4
它们是可以互换的。问题中的错误并不是因为提问者将 any 替换为 interface{}。提问者试图使用一个通用函数和一个非通用函数。这不是同一件事。请参见编辑后的问题。 - icza

6
你的问题与使用any/interface{}无关 —— 它们之间的区别纯粹是外观上的,而是类型推断。从这个playground可以看出,如果你使用显式类型实例化函数,比如printAny[any](nil),它就能工作了。
如果你有一个带有泛型类型的函数,你需要指定类型。然而,Go编译器非常聪明,可以为你推断出一些类型。但是,nil是不可能被推断出来的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接