类型参数中的空白标识符的用途是什么?
What's the purpose of a blank identifier in a type parameter?
这两个函数有什么区别?
func f[_ string, p string](s ...p) {
fmt.Println(s)
}
func f[p string](s ...p) {
fmt.Println(s)
}
为什么一开始还要在类型参数中放置一个空白标识符?
使用下划线 _
代替类型参数名称只是表明该类型参数未在函数范围内使用。
在您的示例代码中,这并没有什么不同; f
的两个版本都可以调用为 f("blah")
而无需提供显式类型参数。但这是可能的,因为约束 string
限制为 精确类型 。它只能是 string
,所以类型推断仍然有效。
如果将其更改为近似类型,则必须显式实例化它:
// can't infer first type param from anywhere
func f[_ ~string, P ~string](s ...P) {
fmt.Println(s)
}
func main() {
// must supply first type param, may omit second one
f[string]("blah")
}
在约束不是精确类型的现实世界场景中,下划线可能会强制调用者指定类型参数:这实际上会破坏向后兼容性,因为客户端代码确实必须被更新以工作。
需要说明的是,这适用于相互独立的类型参数。如果带下划线的类型参数可以从另一个推断出来,它仍然可以在没有显式实例化的情况下编译:
// called as foo("blah")
func foo[T ~string, P *T](t T) {
var p P = &t
fmt.Println(t, p)
}
// still called as foo("blah")
func foo[T ~string, _ *T](t T) {
fmt.Println(t)
}
但是值得注意的是,如果一个函数有两个独立的类型参数,可以从参数中推断出来:
func f[T any, U any](t T, u U) {
fmt.Println(t, u)
}
...使 U
不可推断意味着也删除了 u U
参数,因此您已经有了 backward-incompatible 更改。
// one less argument, requires major version upgrade anyway
func foo[T any, _ any](t T) {
fmt.Println(t, u)
}
使用方法,情况就不同了。由于方法不能指定未在接收者类型上声明的类型参数,因此使用下划线的情况要频繁得多。可能只是碰巧某些方法不需要所有类型参数。然后下划线恰如其分地反映了:
type Foo[T,U any] struct {
ID T
Val U
}
// we do not need to reference U here
func (f *Foo[T,_]) SetID(t T) {
f.ID = t
}
// we do not need to reference T here
func (f *Foo[_,U]) SetVal(v U) {
f.Val = v
}
这两个函数有什么区别?
func f[_ string, p string](s ...p) {
fmt.Println(s)
}
func f[p string](s ...p) {
fmt.Println(s)
}
为什么一开始还要在类型参数中放置一个空白标识符?
使用下划线 _
代替类型参数名称只是表明该类型参数未在函数范围内使用。
在您的示例代码中,这并没有什么不同; f
的两个版本都可以调用为 f("blah")
而无需提供显式类型参数。但这是可能的,因为约束 string
限制为 精确类型 。它只能是 string
,所以类型推断仍然有效。
如果将其更改为近似类型,则必须显式实例化它:
// can't infer first type param from anywhere
func f[_ ~string, P ~string](s ...P) {
fmt.Println(s)
}
func main() {
// must supply first type param, may omit second one
f[string]("blah")
}
在约束不是精确类型的现实世界场景中,下划线可能会强制调用者指定类型参数:这实际上会破坏向后兼容性,因为客户端代码确实必须被更新以工作。
需要说明的是,这适用于相互独立的类型参数。如果带下划线的类型参数可以从另一个推断出来,它仍然可以在没有显式实例化的情况下编译:
// called as foo("blah")
func foo[T ~string, P *T](t T) {
var p P = &t
fmt.Println(t, p)
}
// still called as foo("blah")
func foo[T ~string, _ *T](t T) {
fmt.Println(t)
}
但是值得注意的是,如果一个函数有两个独立的类型参数,可以从参数中推断出来:
func f[T any, U any](t T, u U) {
fmt.Println(t, u)
}
...使 U
不可推断意味着也删除了 u U
参数,因此您已经有了 backward-incompatible 更改。
// one less argument, requires major version upgrade anyway
func foo[T any, _ any](t T) {
fmt.Println(t, u)
}
使用方法,情况就不同了。由于方法不能指定未在接收者类型上声明的类型参数,因此使用下划线的情况要频繁得多。可能只是碰巧某些方法不需要所有类型参数。然后下划线恰如其分地反映了:
type Foo[T,U any] struct {
ID T
Val U
}
// we do not need to reference U here
func (f *Foo[T,_]) SetID(t T) {
f.ID = t
}
// we do not need to reference T here
func (f *Foo[_,U]) SetVal(v U) {
f.Val = v
}