Go 迷思之 Named 和 Unnamed Types

hxzqlh發表於2017-11-28

始發於微信公眾號 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,相信我,你將不可能在這門語言上走遠。

相關文章