【问题标题】:What's the meaning of the new tilde token ~ in Go?Go中新的波浪号令牌〜是什么意思?
【发布时间】:2022-04-05 05:19:43
【问题描述】:

Go 引入了新的令牌 ~

~T 表示底层类型为 T 的所有类型的集合

不过,我看不懂,请人帮忙解释一下。

下面是一个例子。

type Ordered interface {
      Integer | Float | ~string
}

【问题讨论】:

    标签: go generics


    【解决方案1】:

    在泛型提案中,~ 波浪号标记以~T 的形式用于表示基础类型为T 的类型集。

    它在generics proposal 中也被称为“近似”约束元素,它用简单的语言解释了它的好处:

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

    如果您需要正式的参考,语言规范已将基础类型的定义放在its own section

    每个类型 T 都有一个基础类型:如果 T 是预先声明的布尔、数字或字符串类型之一,或者是类型文字,则相应的基础类型是 T 本身。否则,T 的基础类型是T 在其类型声明中引用的类型的基础类型

    这涵盖了类型文字和其他具有绑定标识符的复合类型的非常常见的情况,或者您在预声明的标识符上定义的类型,这是泛型提案中提到的情况:

    // underlying type = struct literal -> itself -> struct { n int }
    type Foo struct {
        n int
    }
    
    // underlying type = slice literal -> itself -> []byte
    type ByteSlice []byte
    
    // underlying type = predeclared -> itself -> int8
    type MyInt8 int8
    
    // underlying type = predeclared -> itself -> string
    type MyString string
    

    实际含义是类型集只有精确元素的接口约束不允许您自己定义类型:

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

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

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

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

    // must bind identifier in order to declare methods
    type ByteSeq []byte
    
    func (b ByteSeq) DoSomething() {}
    

    现在近似元素可以方便地使用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
    }
    

    注意:您不能将近似标记与类型参数一起使用:

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

    【讨论】:

      【解决方案2】:

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

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

      来自docs

      一个接口,表示所有类型的底层类型为 int 并实现了 String 方法。

      interface {
        ~int
        String() string
      }
      

      对于具有“基础类型”int 的类型,这意味着该类型采用以下形式:

      type SomeType int
      

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

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

      【讨论】:

        最近更新 更多