类型参数中的空白标识符的用途是什么?

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
}