> 文章列表 > Go语言规范中的可赋值

Go语言规范中的可赋值

Go语言规范中的可赋值

了解可赋值规范的重要性

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

func main(){var i int = 2pushInt(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 have identical underlying types but are not type parameters and at least one of V or T is not a named 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.

  • 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.

  • x is an untyped constant representable by a value of type T.

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 //类型名字为Dogtype 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} //functionvar x struct{ name string } = struct{ name string }{"name"} //structvar x []int = []int{1,2,3} //slicevar x [3]int = [3]int{1,3,4} //arrayvar x map[int]int = make(map[int]int) //mapvar x chan int = make(chan int) //channelnum := 23var x *int = &num //pointervar x interface{String() stringName() 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 = []stringA1 = A0A2 = struct{ a, b int }A3 = intA4 = func(A3, float64) *A0A5 = func(x int, _ float64) *[]stringB0 A0B1 []stringB2 struct{ a, b int }B3 struct{ a, c int }B4 func(int, float64) *B0B5 func(x int, y float64) *A1C0 = B0D0[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.

类型相同,可以赋值
  • V and T have identical underlying types but are not type parameters and at least one of V or T is not a named type.

不是类型参数,底层类型相同,如果只有一个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 is an untyped constant representable by a value of type T.

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 + agefmt.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,且底层类型必须兼容。

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