Go语言规范中的可赋值

  • Post author:
  • Post category:其他


了解可赋值规范的重要性

当使用type关键字定义类型的时候,会遇到一些问题,如下:

func main(){
    var i int = 2
    pushInt(i) 

}
type MyInt int //基于int定义MyInt
func pushInt(i MyInt){}

结果:调用函数pushInt报错
cannot use i (variable of type int) 
as MyInt value in argument to pushIntcompilerIncompatibleAssign


而相似的,这种调用就不会出错:

func main(){
    var i []int = []int{2,3,4}
    pushInt(i)

}

type MyInt []int //基于[]int 定义MyInt
func pushInt(i MyInt){}

结果:正常编译运行!!!


go语法中的赋值无处不在,赋值操作、调用方法时的receiver赋值、调用方法的parameter赋值、方法返回值的接收变量赋值,赋值即值拷贝,这个大家都懂,可是赋值的类型约束是什么?


赋值原则其实很简单

1、类型相同可以进行赋值
2、类型不同的情况,至少有一个是unnamed type,且底层类型必须兼容。


下面会慢慢讲解。


go语言规范定义


go语言规范中对可赋值的描述比较复杂,说到底就是上面的2个原则,我们先大概看一下规范内容,然后等阐明什么叫类型相同,什么叫底层类型相同,在回过头来理解该规范。


A value x of type V is

assignable

to a



variable



of type T (“x is assignable to T”) if one of the following conditions applies:


  • V and T are identical.


  • V and T are channel types with identical element types, V is a bidirectional channel, and at least one of V or T is not a



    named type



    .


  • T is an interface type, but not a type parameter, and x



    implements



    T.


  • x is the predeclared identifier nil and T is a pointer, function, slice, map, channel, or interface type, but not a type parameter.


Additionally, if x’s type V or T are type parameters, x is assignable to a variable of type T if one of the following conditions applies:


  • x is the predeclared identifier nil, T is a type parameter, and x is assignable to each type in T’s type set.


  • V is not a



    named type



    , T is a type parameter, and x is assignable to each type in T’s type set.


  • V is a type parameter and T is not a named type, and values of each type in V’s type set are assignable to T.


什么叫类型相同


1、named type,有名字的类型,只有名称相同才能称为类型相同


named的type包括


  • predeclared type,即程序预声明的类型,如int byte run string等,这些都是有名字的。

var x int = 20 //x的类型是named type -->int

  • defined type,即通过type关键字定义的类型,定义时根据语法是必须要给定名字的。(注意type declaration和type definition的区别)

type Dog struct{} //类型名字为Dog
type Dog int //类型名字为Dog

type Dog = int //类型名字为int(Dog只是个别名)
type Dog = struct{} //该类型是unnamed(Dog只是个别名)

  • type parameter,类型参数是泛型中的概念,其定义了新的类型,例如[T ~int],类型名为T,底层类型为int(底层类型后面讲)

func name[T ~string](dogName T){} //定义了一个新的类型T
//注意T是一个类型,而func name(T string)中,T是一个变量。

2、literal type,字面量类型没有名称,只要结构相同,类型就相同


composite类型都可以用字面量定义新的类型,如slice channel等的类型都可以用literal来定义,如下列举了几个literal类型定义:

    var x func(string) int = func(s string) int {
        return 1
    } //function
 
    var x struct{ name string } = struct{ name string }{"name"} //struct
 
    var x []int = []int{1,2,3} //slice
 
    var x [3]int = [3]int{1,3,4} //array
 
    var x map[int]int = make(map[int]int) //map
 
    var x chan int = make(chan int) //channel
 
    num := 23
    var x *int = &num //pointer
 
    var x interface{
        String() string
        Name() string
    } = Inner{"name"} //interface


我们发现其实 指针类型、chan、map、array、slice的类型定义,我们平时都是使用unnamed的literal type 形式。因为比较方便。如果我们使用named type反而会比较麻烦

type MyMap map[int]int //这样定义类型就比较麻烦


3、规范中对类型相同的描述


以上两种已经描述何为类型相同,规范中是这样描述的:


A




named type




is always different from any other type. Otherwise, two types are identical if their




underlying




type literals are structurally equivalent; that is, they have the same literal structure and corresponding components have identical types. In detail:


  • Two array types are identical if they have identical element types and the same array length.


  • Two slice types are identical if they have identical element types.


  • Two struct types are identical if they have the same sequence of fields, and if corresponding fields have the same names, and identical types, and identical tags.




    Non-exported




    field names from different packages are always different.


  • Two pointer types are identical if they have identical base types.


  • Two function types are identical if they have the same number of parameters and result values, corresponding parameter and result types are identical, and either both functions are variadic or neither is. Parameter and result names are not required to match.


  • Two interface types are identical if they define the same type set.


  • Two map types are identical if they have identical key and element types.


  • Two channel types are identical if they have identical element types and the same direction.


  • Two




    instantiated




    types are identical if their defined types and all type arguments are identical.


4、小试牛刀(规范中的小练习)


以下类型哪些相同

type (
    A0 = []string
    A1 = A0
    A2 = struct{ a, b int }
    A3 = int
    A4 = func(A3, float64) *A0
    A5 = func(x int, _ float64) *[]string

    B0 A0
    B1 []string
    B2 struct{ a, b int }
    B3 struct{ a, c int }
    B4 func(int, float64) *B0
    B5 func(x int, y float64) *A1

    C0 = B0
    D0[P1, P2 any] struct{ x P1; y P2 }
    E0 = D0[int, string]
)


相同的类型:

A0, A1, and []string
A2 and struct{ a, b int }
A3 and int
A4, func(int, float64) *[]string, and A5
B0 and C0
D0[int, string] and E0
[]int and []int
struct{ a, b *B5 } and struct{ a, b *B5 }
func(x int, y float64) *[]string, func(int, float64) (result *[]string), and A5


B0 and B1 are different because they are new types created by distinct



type definitions



; func(int, float64) *B0 and func(x int, y float64) *[]string are different because B0 is different from []string; and P1 and P2 are different because they are different type parameters. D0[int, string] and struct{ x int; y string } are different because the former is an



instantiated



defined type while the latter is a type literal (but they are still



assignable



)


什么叫底层类型相同


什么叫底层类型


每种类型都有其底层类型


  • 上面提到的predeclared类型和literal类型其底层就是其本身

var x int //变量x的类型是predeclared 的int,int的底层类型是int
var x []int //类型是literal的[]int,其底层类型是[]int

  • 指向类型的底层类型,是其指向的类型的底层类型。有点拗口,上栗子

type MyInt int //MyInt指向int,int的底层类型是int,那么结果是int
type YourInt MyInt //YourInt指向MyInt,那么就是MyInt的底层类型,那么结果就是int
type HisInt YourInt //HisInt指向YourInt,以此类推,那么结果就是int

  • 类型参数的底层类型,是其约束类型。

func name[T ~string](n  T){}//类型参数定义了新的类型T,T的底层类型就是string


知道底层类型是什么,那么按第一小节“什么叫类型相同”中的规则进行对比,即可知道两个类型的底层类型是否相同。


回过来看可赋值的规范定义


下面会对规范中可赋值定义进行一句句解释:


A value x of type V is

assignable

to a



variable



of type T (“x is assignable to T”) if one of the following conditions applies:


这一部分讲解非type parameter(类型参数)的情形:


  • V and T are identical.

类型相同,可以赋值
不是类型参数,底层类型相同,如果只有一个unnamed type那么底层类型相同,两个
都是unnamed type的话是相同类型。

  • V and T are channel types with identical element types, V is a bidirectional channel, and at least one of V or T is not a



    named type



    .

channel元素相同,底层数据类型相同。如果一个unnamed type的话,那么底层数据相同。如果两个都是
unnamed type的话,那么底层数据相同,甚至是类型完全相同。
var c <- chan int = make(chan int) //类型不同,但底层类型相同
var c chan int = make(chan int) //类型相同

  • T is an interface type, but not a type parameter, and x



    implements



    T.

x和T必须有is a的关系。

  • x is the predeclared identifier nil and T is a pointer, function, slice, map, channel, or interface type, but not a type parameter.

x是nil,因为nil是预声明标识符,而不是类型。
可以赋值给pointer,function....(引用类型和interface类型)
x是untyped int ,是unnamed。但是和int32 底层类型相同,所以可以赋值
const x = 1 << 20 //x在初始化的时候是untyped int(x的取值范围可以超过 1<<64的而x处不会报错)
func main() {
	var y int32 = x
 }


这一部分讲解type parameter的情形:


Additionally, if x’s type V or T are type parameters, x is assignable to a variable of type T if one of the following conditions applies:


  • x is the predeclared identifier nil, T is a type parameter, and x is assignable to each type in T’s type set.

x是nil是标识符而不是named type,T是type parameter有名字,类型不同。
T类型参数的类型集是指针类型,可以接受nil
func name3[X *int](age X) {
	age = nil
}

  • V is not a



    named type



    , T is a type parameter, and x is assignable to each type in T’s type set.

V是unnamed那么就不会和T的类型不同,底层类型相同即可
func name[X ~int](age X) {
	a := 20 + age
	fmt.Println(a)
	age = 30 // age = a会报错,因为a是named类型,而age = 30就不会报错
}

  • V is a type parameter and T is not a named type, and values of each type in V’s type set are assignable to T.

age是type parameter有unnamed的,而x是literal没名字,类型不同,但底层类型一样
func name2[X IntArr](age X) {
	var x map[int]int = age 
}
type IntArr map[int]int


完全符合我们的可赋值原则


1、类型相同可以进行赋值


2、类型不同的情况,至少有一个是unnamed type,且底层类型必须兼容。


描述可能不够准确,望网络大佬们指正。

🙅



版权声明:本文为dshf_1原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。