新的波浪线符号~在Go语言中的含义是什么?

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

What's the meaning of the new tilde token ~ in Go?

问题

Go引入了新的标记~

> ~T表示具有基础类型T的所有类型的集合

然而,我无法理解它,需要有人帮助解释。

以下是一个示例。

type Ordered interface {
      Integer | Float | ~string
}
英文:

Go introduces the new token ~.

> ~T means the set of all types with underlying type T

However, I could not understand it, ask someone to help explain.

The following is an example.

type Ordered interface {
      Integer | Float | ~string
}

答案1

得分: 47

在Go泛型中,波浪号(~)用于表示形式为~T的类型集合,其中T是其底层类型。

在泛型提案中,它也被称为“近似”约束元素,泛型提案中解释了它在简单语言中的用途:

单独列出一个类型是没有用的。对于约束满足,我们希望能够说不仅仅是int,而是“任何底层类型为int的类型”。[...] 如果程序使用type MyString string,程序可以使用类型为MyString的值使用<运算符。应该可以使用类型MyString实例化[一个函数]。

如果你想要一个正式的参考,语言规范已经在自己的章节中定义了底层类型

每个类型T都有一个底层类型:如果T是预声明的布尔、数值或字符串类型,或者是类型字面量,则相应的底层类型是T本身。否则,T的底层类型是T在其类型声明中引用的类型的底层类型

这涵盖了类型字面量和其他带有绑定标识符的复合类型或你在预声明标识符上定义的类型的常见情况,这也是泛型提案中提到的情况:

// 底层类型 = 结构体字面量 -> 本身 -> struct { n int }
type Foo struct {
    n int
}

// 底层类型 = 切片字面量 -> 本身 -> []byte
type ByteSlice []byte

// 底层类型 = 预声明 -> 本身 -> int8
type MyInt8 int8

// 底层类型 = 预声明 -> 本身 -> string
type MyString string

实际的影响是,一个接口约束,其类型集合只有精确元素,不允许使用你自己定义的类型:

// 假设没有近似元素的约束
type ExactSigned interface {
	int | int8 | int16 | int32 | int64
}

// 不能使用MyInt8实例化
func echoExact[T ExactSigned](t T) T { return t }

// constraints.Signed使用近似元素,例如~int8
// 可以使用MyInt8实例化
func echo[T constraints.Signed](t T) T { return t }

与其他约束元素一样,你可以在联合中使用近似元素,例如constraints.Signed或带有或不带有语法糖的匿名约束。值得注意的是,只有一个近似元素的语法糖是有效的:

// 匿名约束
func echoFixedSize[T interface { ~int8 | ~int32 | ~int64 }](t T) T { 
    return t 
}

// 带有语法糖的匿名约束
func echoFixedSizeSugar[T ~int8 | ~int32 | ~int64](t T) T { 
    return t 
}

// 带有语法糖和一个元素的匿名约束
func echoFixedSizeSugarOne[T ~int8](t T) T { 
    return t 
}

如上所述,近似元素的常见用例是具有方法的复合类型(切片、结构体等)。在这种情况下,你必须绑定标识符:

// 必须绑定标识符以声明方法
type ByteSeq []byte

func (b ByteSeq) DoSomething() {}

现在,近似元素很方便,可以允许使用ByteSeq进行实例化:

// 不允许ByteSeq,或者必须先转换函数参数
func foobar[T interface { []byte }](t T) { /* ... */ }


// 允许ByteSeq
func bazquux[T interface { ~[]byte }](t T) { /* ... */ }

func main() {
    b := []byte{0x00, 0x01}
    seq := ByteSeq{0x02, 0x03}

    foobar(b)           // 可行
    foobar(seq)         // 编译器错误
    foobar([]byte(seq)) // 可行,允许推断
    foobar[[]byte](seq) // 可行,显式实例化,然后可以将seq分配给参数类型[]byte

    bazquux(b)          // 可行
    bazquux(seq)        // 可行
}

注意:你不能在类型参数中使用近似标记:

// 无效!
type AnyApprox[T any] interface {
    ~T
}
英文:

In Go generics, the ~ tilde token is used in the form ~T to denote the set of types whose underlying type is T.

It was also called "approximation" constraint element in the generics proposal, which explains what it's good for in plain language:

> Listing a single type is useless by itself. For constraint satisfaction, we want to be able to say not just int, but “any type whose underlying type is int”. [...] If a program uses type MyString string, the program can use the &lt; operator with values of type MyString. It should be possible to instantiate [a function] with the type MyString.

If you want a formal reference, the language spec has placed the definition of underlying types in its own section:

> Each type T has an underlying type: If T is one of the predeclared boolean, numeric, or string types, or a type literal, the corresponding underlying type is T itself. Otherwise, T's underlying type is the underlying type of the type to which T refers in its type declaration.

This covers the very common cases of type literals and other composite types with bound identifiers, or types you define over predeclared identifiers, which is the case mentioned in the generics proposal:

// underlying type = struct literal -&gt; itself -&gt; struct { n int }
type Foo struct {
    n int
}

// underlying type = slice literal -&gt; itself -&gt; []byte
type ByteSlice []byte

// underlying type = predeclared -&gt; itself -&gt; int8
type MyInt8 int8

// underlying type = predeclared -&gt; itself -&gt; string
type MyString string

The practical implication is that an interface constraint whose type set has only exact elements doesn't allow your own defined types:

// hypothetical constraint without approximation elements
type ExactSigned interface {
	int | int8 | int16 | int32 | int64
}

// CANNOT instantiate with MyInt8
func echoExact[T ExactSigned](t T) T { return t }

// constraints.Signed uses approximation elements e.g. ~int8
// CAN instantiate with MyInt8
func echo[T constraints.Signed](t T) T { return t }

As with other constraint elements, you can use the approximation elements in unions, as in constraints.Signed or in anonymous constraints with or without syntactic sugar. Notably the syntactic sugar with only one approx element is valid:

// anonymous constraint
func echoFixedSize[T interface { ~int8 | ~int32 | ~int64 }](t T) T { 
    return t 
}

// anonymous constraint with syntactic sugar
func echoFixedSizeSugar[T ~int8 | ~int32 | ~int64](t T) T { 
    return t 
}

// anonymous constraint with syntactic sugar and one element
func echoFixedSizeSugarOne[T ~int8](t T) T { 
    return t 
}

As anticipated above, a common use case for approximation elements is with composite types (slices, structs, etc.) that need to have methods. In that case you must bind the identifier:

// must bind identifier in order to declare methods
type ByteSeq []byte

func (b ByteSeq) DoSomething() {}

and now the approximation element is handy to allow instantiation with ByteSeq:

// ByteSeq not allowed, or must convert func argument first
func foobar[T interface { []byte }](t T) { /* ... */ }


// ByteSeq allowed
func bazquux[T interface { ~[]byte }](t T) { /* ... */ }

func main() {
    b := []byte{0x00, 0x01}
    seq := ByteSeq{0x02, 0x03}

    foobar(b)           // ok
    foobar(seq)         // compiler error
    foobar([]byte(seq)) // ok, allows inference
    foobar[[]byte](seq) // ok, explicit instantiation, then can assign seq to argument type []byte

    bazquux(b)          // ok
    bazquux(seq)        // ok
}

NOTE: you can not use the approximation token with a type parameter:

// INVALID!
type AnyApprox[T any] interface {
    ~T
}

答案2

得分: 9

不仅有新的标记,还有新的接口语法。除了方法约束之外,您可以在接口中声明类型约束。

要满足一个接口,类型必须同时满足方法约束和类型约束。

根据文档

表示所有具有基础类型为int且实现String方法的类型的接口。

interface {
  ~int
  String() string
}

对于一个类型具有"基础类型"为int,这意味着该类型采用以下形式:

type SomeType int

并且要满足方法约束,必须声明一个具有指定签名的方法:

func (v SomeType) String() string {
  return fmt.Sprintf("%d", v)
}
英文:

There is not just the new token, but the new syntax for interfaces. You can declare an interface with type constraints in addition to method constraints.

To satisfy an interface, the type must satisfy both the method constraints and the type constraints.

From the docs:
> An interface representing all types with underlying type int which implement the String method.
> go
&gt; interface {
&gt; ~int
&gt; String() string
&gt; }
&gt;

For a type to have an "underlying type" of int, that means the type takes the following form:

type SomeType int

And to satisfy the method constraint, there must be declared a method with the specified signature:

func (v SomeType) String() string {
  return fmt.Sprintf(&quot;%d&quot;, v)
}

huangapple
  • 本文由 发表于 2022年1月28日 10:42:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/70888240.html
匿名

发表评论

匿名网友

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

确定