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,相信我,你將不可能在這門語言上走遠。
相關文章
- 譯|There Are No Reference Types in GoGo
- TypeScript 之 Object TypesTypeScriptObject
- TypeScript 之 Indexed Access TypesTypeScriptIndex
- TypeScript 之 Conditional TypesTypeScript
- 關於UNNAMED00004
- RabbitMQ 訊息佇列之 Exchange TypesMQ佇列
- 破解敏捷測試的迷思敏捷測試
- 遊戲策劃之遊戲設計中遇到的四個迷思(轉)遊戲設計
- Swift之集合型別 (Collection Types)(集合篇)Swift型別
- 小丸子學Redis系列之——Data types(一)Redis
- EF Core – Owned Entity Types & Complex Types
- TypeScript @typesTypeScript
- stock Types
- Go之NSQ簡介,原理和使用Go
- Postgres 9.2 新特性之:範圍型別 (Range Types)型別
- Types of Locks (340)
- Go之傳送釘釘和郵箱Go
- laravel named routeLaravel
- Two Types of Error in JAVAErrorJava
- Understanding Service Types
- Scala的Abstract Types
- Types of SAP Support PackagesPackage
- types of undo segments(ZT)
- Behind RabbitMQ Exchange TypesMQ
- Trusted Types APIRustAPI
- 產品的工具化與社會化迷思
- Go之Gorm和BeegoORM簡介及配置使用GoORM
- Go語言系列(三)之陣列和切片Go陣列
- Java,Go和Rust之間的比較 - DexterJavaGoRust
- go語言入門之-函式和方法Go函式
- Dagger 2 系列(四) -- 基礎篇:@Named 和 @Qualifier
- ImportError: No module named `defusedxml`ImportErrorXML
- ImportError: No module named yamlImportErrorYAML
- ImportError: No module named utilsImportError
- ModuleNotFoundError: No module named 'sqlite'ErrorSQLite
- ImportError: No module named cElementTreeImportError
- SpringBoot 學習之 No bean named ‘xxx’ availableSpring BootBeanAI
- Spring Boot學習之No bean named 'entityManagerFactory' available異常Spring BootBeanAI