Go 迷思之 Named 和 Unnamed Types

始发于微信公众号 Go 迷思之 Named 和 Unnamed Types

先来热身一下,下面的代码能编译吗?为什么?

package main

type stack []uintptr

func callers() stack {
    return make([]uintptr, 20)
}

func main() {
    callers()
}

(此处省略一分钟冥思苦想状....)

好啦,不用多想了,当然可以编译。

但是……这个问题重要吗?

是的,很重要。

如果上面这份代码不能编译,那意味着你无法写这样的代码:

type stack []uintptr
var st stack = make([]uintptr, 20)

而我们知道,这样的代码几乎无处不在。

再来,下面的代码能通过编译吗?

type T int

func F(t T) {}

func main() {
    var q int
    F(q)
}

结合你平时写的代码,再思考一分钟……

Ops, it couldn't。

稍微改动如下,它能通过编译吗?

type T []int

func F(t T) {}

func main() {
    var q []int
    F(q)
}

Yes, it does.

Surprised?! How could this happen?

Read The Fxxking Manual

言归正传,先来看下这又臭又长的 《Go 规范手册》 是怎么解释 Types 的。

A type determines a set of values together with operations and methods specific to those values. A type may be denoted by a type name, if it has one, or specified using a type literal, which composes a type from existing types.

Named instances of the boolean, numeric, and string types are predeclared. Other named types are introduced with type declarations. Composite types—array, struct, pointer, function, interface, slice, map, and channel types—may be constructed using type literals.

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.

Named vs Unnamed Type

Named types 有两类:

  • 内置的类型,比如 int, int64, float, string, bool,
  • 用 type 关键字声明的类型,比如 type Foo string

Unamed types:基于已有的 named types 声明出的组合类型,uname types 在 Go 里俯拾皆是。比如 struct{}、[]string、interface{}、map[string]bool、[20]float32……

Named types 可以作为方法的接受者, unnamed type 却不能。比如:

type Map map[string]string

// ok
func (m Map) Set(key string, value string){
    m[key] = value 
}

// invalid receiver type map[string]string (map[string]string is an unnamed type)
func (m map[string]string) Set(key string, value string){
    m[key] = value 
}

Underlying Type

每种类型 T 都有一个底层类型:如果 T 是预声明类型或者 类型字面量(笔者注:type literal 翻译成类型字面量,地道不?) ,它的底层类型就是 T 本身,否则,T 的底层类型是其类型声明中引用的类型的底层类型。

type (
    B1 string
    B2 B1 
    B3 []B1
    B4 B3 
)

string, B1 和 B2 的底层类型是 string.

B2 引用了 B1,那么 B2 的底层类型其实是 B1 的底层类型,而 B1 又引用了 string,那么 B1 的底层类型其实是 string 的底层类型,很明显,string 的底层类型就是string,最终 B2 的底层类型是 string。

[]B1, B3, 和 B4 的底层类型是 []B1.

[]B1 是类型字面量,因此它的底层类型就是它本身。

所有基于相同 unnamed types 声明的变量的类型都相同,而对于 named types 变量而言,即使它们的底层类型相同,它们也是不同类型。

// x 是 unnamed types
var x struct{ I int }

// x 和 x2 类型相同
var x2 struct{ I int }

// y 是 named type
type Foo struct{ I int }
var y Foo

// y 和 z 类型不同
type Bar struct{ I int }
var z Bar

Assignability

不同类型的变量之间是不能赋值的。

type MyInt int
var i int = 2
var i2 MyInt = 4
i = i2  // error: cannot use i2 (type MyInt) as type int in assignment

你不能把 i2 赋值给 i,因为它们的类型不同,虽然它们的底层类型都是 int。

对于那些拥有相同底层类型的变量而言,还需要理解另外一个重要概念:可赋值性。在 Assignability 的六大准则中,其中有一条:

x's type V and T have identical underlying types and at least one of V or T is not a defined type.

也就是说底层类型相同的两个变量可以赋值的条件是:至少有一个不是 named type。

x  = y   // ok
y  = x   // ok
x  = x2  // ok
y  = z   // error: cannot use y (type Foo) as type Bar in assignment 

现在,你知道“为什么开头那两份代码为什么一个能编译另一个不能”了吧。

Type Embedding

当你使用 type 声明了一个新类型,它不会继承原有类型的方法集。

package main

type User struct {
    Name string
}

func (u *User) SetName(name string) {
    u.Name = name
}

type Employee User 

func main(){
    employee := new(Employee)
    employee.SetName("Jack"). 
    // error employee.SetName undefined (type *Employee has no field or method SetName)
}

作为一个小技巧,你可以将原有类型作为一个匿名字段内嵌到 struct 当中来继承它的方法,这样的 struct 在 Go 代码中太常见不过了。

比如:

package main

type User struct {
    Name string
}

func (u *User) SetName(name string) {
    u.Name = name
}

type Employee struct {
    User       // annonymous field
    Title      string
}

func main(){
    employee := new(Employee)
    employee.SetName("Jack")
}

Last But Not Least

Go 里面关于类型 Types 的一些规定有时候让初学者丈二和尚摸不着头脑,而 Types 几乎是任何一门编程语言的基石,如果你不能理解 Go 里面最基本的概念之一:Types,相信我,你将不可能在这门语言上走远。

0 个评论

要回复文章请先登录注册