這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
目錄 [−]
- 類型別名
- 類型命名和型別宣告的區別
- 類型迴圈
- 可匯出性
- 方法集
- byte 和 rune 類型
- 參考資料
如你所知, 類型別名(type aliases) 最終還是加入到Go 1.9中, Go 1.9 beta2今天已經發布了, 正式版預計8月初發布, 是時候深入瞭解一下它的新特性了,本文介紹的就是它的重要的新特性之一: 類型別名。
當然,如果你想嘗試這些新特性,需要安裝Go 1.9的版本,目前是beta2版,可以在官方網站下載。
類型別名主要解決什麼問題,為什麼需要這個特性? Russ Cox 的論文Codebase Refactoring (with help from Go)介紹了它的背景。類型別名主要用在:
- 在大規模的重構項目代碼的時候,尤其是將一個類型從一個包移動到另一個包中的時候,有些代碼使用新包中的類型,有些代碼使用舊包中的類型, 比如
context
- 允許一個龐大的包分解成內部的幾個小包,但是小包中的類型需要集中暴漏在上層的大包中
類型別名
類型別名的文法如下:
它和類型定義(type definition)類似,僅僅是在identifier
和Type
之間加了一個等號=
,但是和類型定義區別很大,這一點會在後面專門比較。
下面這個例子就是為字串string
類型定義了一個別名S
,你可以聲明變數、常量為S
類型,將字串賦值給它,它和字串類型幾乎一模一樣。
12345678910 |
package mainimport "fmt"type S = stringfunc main() {var s S = "hello world"fmt.Println(s)} |
當然, 你可以為任意的類型定義類型別名,語言規範中沒有限制,可以為數組、結構體、指標、函數、介面、Slice、Map、Channel定義別名,甚至你還可以為通過類型定義(type definition)的類型定義別名,更甚者是你可以為別名定義別名。
比如下面這個例子, 為函數類型func()
定義了一個別名F
:
12345678910111213 |
package mainimport "fmt"type F = func()func main() {var foo F = func() {fmt.Println("hello type aliases")}foo()} |
又如下面的代碼,為interface{}
定義了別名G
:
12345678910 |
package mainimport "fmt"type G = interface{}func main() {var g G = "hello world"fmt.Println(g)} |
類型別名還可以為其它包中的類型定義別名,只要這個類型在其它包中是exported
的:
12345678910111213 |
package mainimport ("fmt""time")type MyTime = time.Timefunc main() {var t MyTime = time.Now()fmt.Println(t)} |
類型命名和型別宣告的區別
記住下面一句話:
類型別名和原類型完全一樣,只不過是另一種叫法而已
這句話隱藏著很多的智慧,你可以慢慢體會。
完全一樣(identical types)意味著這兩種類型的資料可以互相賦值,而類型定義要和原始類型賦值的時候需要類型轉換(Conversion T(x)
)。
下面這個例子中,v
是整數類型,可以直接賦值給d
,因為d的類型是D
,是是整數的別名。而var i I = v
這一句會出錯,因為I
和整數是兩個類型。
所以類型別名和類型定義最大的區別在於:類型別名和原類型是相同的,而類型定義和原類型是不同的兩個類型。
12345678910 |
package maintype D = inttype I intfunc main() {v := 100var d D = vvar i I = v} |
比如類型定義type Tnamed Tunderlying
,系列類型和組合類別型是不同的:
Tnamed
和 Tunderlying
*Tnamed
和 *Tunderlying
chan Tnamed
和 chan Tunderlying
func(Tnamed)
和 func(Tunderlying)
interface{ M() Tnamed }
和 interface{ M() Tunderlying }
但是對於別名type T1 = T2
,下列類型和組合類別型是相同的:
T1
和 T2
*T1
和 *T2
chan T1
和 chan T2
func(T1)
和 func(T2)
interface{ M() T1 }
和 interface{ M() T2 }
還有一個重要的區別在於類型定義的類型的方法集和原始類型的方法集沒有任何關係,而類型別名和原始類型的方法集是一樣的,下面再介紹。
既然類型別名和原類型是相同的,那麼在`switch - type中,你不能將原類型和類型別名作為兩個分支,因為這是重複的case:
123456789101112131415161718 |
package mainimport "fmt"type D = intfunc main() {var v interface{} var d D = 100v = dswitch i := v.(type) {case int:fmt.Println("it is an int:", i)// case D:// fmt.Println("it is D type:", i)}} |
類型迴圈
類型別名在定義的時候不允許出現迴圈定義別名的情況,如下面所示:
12 |
type T1 = T2type T2 = T1 |
上面的例子太明顯,下面這個例子比較隱蔽,也是迴圈定義類型別名的情況,當然這些在編譯代碼的時候編譯器會幫你檢查,如果出現迴圈定義的情況會出錯。
12345 |
type T1 = struct {next *T2}type T2 = T1 |
可匯出性
如果定義的類型別名是exported
(首字母大寫)的,那麼別的包中就可以使用,它和原始類型是否可exported
沒關係。也就是說,你可以為unexported
類型定義一個exported
的類型別名,如下面的例子:
12345 |
type t1 struct {S string}type T2 = t1 |
方法集
既然類型別名和原始類型是相同的,那麼它們的方法集也是相同的。
下面的例子中T1
和T3
都有say
和greeting
方法。
1234567891011121314151617 |
type T1 struct{}type T3 = T1func (t1 T1) say(){}func (t3 *T3) greeting(){}func main() {var t1 T1// var t2 T2var t3 T3t1.say()t1.greeting()t3.say()t3.greeting()} |
如果類型別名和原始類型定義了相同的方法,代碼編譯的時候會報錯,因為有重複的方法定義。
另一個有趣的現象是 embedded type
, 比如下面的例子, T3
是T1
的別名。在定義結構體S
的時候,我們使用了匿名嵌入類型,那麼這個時候調用s.say
會怎麼樣呢? 實際是你會編譯出錯,因為s.say`不知道該調用
s.T1.say還是
s.T3.say`,所以這個時候你需要明確的調用。
12345678910111213 |
type T1 struct{}type T3 = T1func (t T1) say(){}type S struct {T1T3}func main() {var s Ss.say()} |
進一步想,這樣是不是我們可以為其它庫中的類型增加新的方法了, 比如為標準庫的time.Time
增加一個滴答方法:
123456789 |
type NTime = time.Timefunc (t NTime) Dida() {fmt.Println("嘀嗒嘀嗒嘀嗒嘀嗒搜尋")}func main() {t := time.Now()t.Dida()} |
答案是: NO, 編譯的時候會報錯: cannot define new methods on non-local type time.Time
。
byte 和 rune 類型
在Go 1.9中, 內部其實使用了類型別名的特性。 比如內建的byte
類型,其實是uint8
的類型別名,而rune
其實是int32
的類型別名。
12345678 |
// byte is an alias for uint8 and is equivalent to uint8 in all ways. It is// used, by convention, to distinguish byte values from 8-bit unsigned// integer values.type byte = uint8// rune is an alias for int32 and is equivalent to int32 in all ways. It is// used, by convention, to distinguish character values from integer values.type rune = int32 |
參考資料
- https://github.com/golang/proposal/blob/master/design/18130-type-alias.md
- https://github.com/golang/go/issues/18130
- https://talks.golang.org/2016/refactor.article
- https://github.com/golang/go/issues/16339