目錄
- 什麼是 Go 的型別系統?
- 基本型別與型別轉換
- Struct 結構體
- Method 與 Receiver
- Interface 介面
- Embedding 嵌入
- Type Assertion 與 Type Switch
- Struct Tags
- 實戰範例
- 最佳實踐
- 常見問題
- 總結
- 參考資源
什麼是 Go 的型別系統?
Go 是靜態型別 + 結構型介面(structural typing)的語言。
核心特點
- 🎯 顯式型別:所有變數編譯時就有確定型別
- ⚡ 結構型介面:實作介面不用宣告
implements,只要方法集對得上就算 - 🔧 無繼承:用 embedding 與 composition 取代
- 📦 無泛型對偶 OOP:用介面表達多型
- 🚀 方法可以掛在任何自訂型別:不限定 struct
跟其他語言對照
| 概念 | Go | Java/C# | Python |
|---|---|---|---|
| 物件 | struct + method | class | class |
| 繼承 | 沒有,用 embedding | extends | 多重繼承 |
| 介面實作 | 隱式(鴨子型別) | implements 宣告 | duck typing |
| 多型 | interface | abstract / interface | 鴨子型別 |
| 抽象類別 | 沒有 | abstract class | abstract base class |
基本型別與型別轉換
內建型別
// 數值
var i int // 平台相依(32 或 64 bit)
var i8 int8 // -128 ~ 127
var i32 int32 // ±21 億
var i64 int64 // ±9.2 × 10^18
var u uint // 無號
var f32 float32
var f64 float64 // 一般用這個
var c complex128 // 複數
// 文字
var s string
var r rune // Unicode code point (int32)
var b byte // 別名 uint8
// 布林
var b bool
// 指標
var p *int
// 容器
var arr [10]int // 陣列(長度是型別一部分)
var sl []int // slice
var m map[string]int // map
var ch chan int // channel
顯式型別轉換
Go 不做隱式轉換,連 int → int64 都要明寫:
var i int = 10
var i64 int64 = int64(i) // 必須顯式
// ❌ 編譯錯誤
var x int = 10
var y int64 = x // cannot use x (type int) as type int64
自訂型別(type alias vs defined type)
// Defined type — 新型別
type UserID int
type Celsius float64
// Type alias — 同型別不同名
type MyInt = int // MyInt 跟 int 完全相同
defined type 跟原型別不能互相賦值:
var id UserID = 100
var n int = id // ❌ 編譯錯
var n int = int(id) // ✅ 需要顯式轉
這設計可以防止單位混淆:
type Meters float64
type Feet float64
func walk(m Meters) { ... }
var f Feet = 100
walk(f) // ❌ 編譯錯,不能傳 Feet 到要 Meters 的 function
Struct 結構體
基本宣告
type User struct {
ID int
Name string
Email string
Active bool
}
// 建立 struct
u1 := User{ID: 1, Name: "Alice", Email: "a@example.com", Active: true}
// 部分初始化(其他欄位用 zero value)
u2 := User{Name: "Bob"}
// 用 new 取得 pointer(很少用)
u3 := new(User) // *User,所有欄位 zero value
// 取 pointer 的慣例
u4 := &User{Name: "Charlie"} // *User
Zero Value
Go 的型別都有 zero value(不需要建構子就能用):
| 型別 | Zero Value |
|---|---|
| 數值 | 0 |
string |
""(空字串) |
bool |
false |
| pointer / interface / slice / map / chan / func | nil |
| struct | 每個欄位各自的 zero value |
匿名 struct
// 一次性使用,不命名型別
config := struct {
Host string
Port int
}{
Host: "localhost",
Port: 8080,
}
常用在測試的 table-driven test。
Struct 比較
兩個 struct 是否相等 → 所有可比較欄位都相等才算相等。
u1 := User{ID: 1, Name: "Alice"}
u2 := User{ID: 1, Name: "Alice"}
fmt.Println(u1 == u2) // true
注意:含有 slice / map / func 的 struct 不能用 == 比較(會編譯錯)。需要用 reflect.DeepEqual。
Method 與 Receiver
Method 是「掛在型別上的 function」。
基本語法
type Rectangle struct {
Width, Height float64
}
// Method
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
// 使用
r := Rectangle{Width: 3, Height: 4}
fmt.Println(r.Area()) // 12
Pointer Receiver vs Value Receiver
type Counter struct {
count int
}
// Value receiver — 拷貝 receiver
func (c Counter) ReadValue() int {
return c.count
}
// Pointer receiver — 可修改 receiver
func (c *Counter) Increment() {
c.count++
}
c := Counter{}
c.Increment() // count = 1(Go 自動取 &c)
c.Increment() // count = 2
fmt.Println(c.ReadValue()) // 2
何時用 Pointer Receiver?
| 情境 | Receiver |
|---|---|
| 需要修改 receiver | Pointer |
| Receiver 是大 struct(拷貝成本高) | Pointer |
| Receiver 含 mutex / sync 等不可拷貝物件 | Pointer |
| 小型 immutable 型別(int wrapper) | Value 也行 |
| 一致性:型別的多個 method 應該都用同一種 | 一致為主 |
慣例:拿不定就用 pointer receiver。
Method 可以掛在任何自訂型別
不限定 struct,只要是自訂 type 都行:
type StringSlice []string
func (s StringSlice) Contains(target string) bool {
for _, v := range s {
if v == target {
return true
}
}
return false
}
ss := StringSlice{"apple", "banana"}
ss.Contains("apple") // true
type Celsius float64
func (c Celsius) Fahrenheit() float64 {
return float64(c)*9/5 + 32
}
但不能對其他套件的型別加 method:
// ❌ int 是 builtin
func (i int) Double() int { return i * 2 }
// compile error
Interface 介面
Interface 定義方法集,任何實作完整方法集的型別都自動實作該介面(結構型介面 / structural typing)。
基本定義
type Shape interface {
Area() float64
Perimeter() float64
}
任何有這兩個 method 的型別都「自動」實作了 Shape:
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 { return r.Width * r.Height }
func (r Rectangle) Perimeter() float64 { return 2 * (r.Width + r.Height) }
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 { return math.Pi * c.Radius * c.Radius }
func (c Circle) Perimeter() float64 { return 2 * math.Pi * c.Radius }
// 使用
var s Shape
s = Rectangle{Width: 3, Height: 4}
fmt.Println(s.Area()) // 12
s = Circle{Radius: 5}
fmt.Println(s.Area()) // 78.54
不用 implements Shape 宣告,編譯器自動驗證。
空介面 interface{} / any
// any 是 interface{} 的別名(Go 1.18+)
var x any = 42
x = "hello"
x = struct{}{}
any 可以裝任何東西,但取出來需要 type assertion(見下節)。
介面組合
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
// 組合
type ReadWriter interface {
Reader
Writer
}
ReadWriter 自動包含 Read 與 Write 兩個 method。
介面的 nil 陷阱
type MyError struct{}
func (e *MyError) Error() string { return "..." }
func foo() error {
var e *MyError = nil
return e // ⚠️ 介面值非 nil!
}
func main() {
err := foo()
if err != nil {
fmt.Println("err 不是 nil") // 印出這個
}
}
原因:介面值由(型別資訊, 值資訊)組成。即使值是 nil,型別資訊存在 → 整個介面非 nil。
規則:要回傳 nil error,就明確 return nil,不要回傳具名 nil pointer。
Embedding 嵌入
Go 沒有繼承,用 embedding 達成「方法繼承 + 欄位透傳」效果。
Struct 嵌入
type Animal struct {
Name string
Age int
}
func (a Animal) Greet() string {
return "I am " + a.Name
}
type Dog struct {
Animal // 嵌入 Animal(沒寫欄位名)
Breed string
}
// 使用
d := Dog{
Animal: Animal{Name: "Rex", Age: 3},
Breed: "Labrador",
}
fmt.Println(d.Name) // "Rex"(直接存取嵌入欄位)
fmt.Println(d.Greet()) // "I am Rex"(直接呼叫嵌入 method)
fmt.Println(d.Animal.Name) // 也行,明確
介面嵌入
type Closer interface {
Close() error
}
type ReadCloser interface {
Reader // 嵌入 Reader
Closer // 嵌入 Closer
}
Method 覆寫(shadowing)
外層型別可以「覆寫」嵌入型別的 method:
type Animal struct{}
func (a Animal) Sound() string { return "(silent)" }
type Dog struct{ Animal }
func (d Dog) Sound() string { return "Woof" } // 覆寫
d := Dog{}
fmt.Println(d.Sound()) // "Woof"
fmt.Println(d.Animal.Sound()) // "(silent)"
Embedding ≠ Inheritance
雖然行為像繼承,但型別關係不同:
func describe(a Animal) { ... }
d := Dog{}
describe(d) // ❌ Dog 不是 Animal
describe(d.Animal) // ✅ 明確取出嵌入欄位
Go 偏好 composition over inheritance。
Type Assertion 與 Type Switch
當你有一個 interface 值,想知道實際型別並取出,用 type assertion 或 type switch。
Type Assertion
var x any = "hello"
// 1. 單回傳值(失敗會 panic)
s := x.(string)
fmt.Println(s) // "hello"
n := x.(int) // panic: interface conversion: any is string, not int
// 2. 雙回傳值(安全)
s, ok := x.(string)
if ok {
fmt.Println(s)
}
n, ok := x.(int)
if !ok {
fmt.Println("不是 int")
}
Type Switch
多型別檢查用 type switch 更乾淨:
func describe(i any) string {
switch v := i.(type) {
case int:
return fmt.Sprintf("int: %d", v)
case string:
return fmt.Sprintf("string: %q", v)
case bool:
return fmt.Sprintf("bool: %t", v)
case []int:
return fmt.Sprintf("[]int 長度 %d", len(v))
case nil:
return "nil"
default:
return fmt.Sprintf("未知型別: %T", v)
}
}
介面對介面的 assertion
type Reader interface { Read(...) }
type Writer interface { Write(...) }
func process(r Reader) {
// 嘗試把 Reader 升級成 Writer
if w, ok := r.(Writer); ok {
// 同時實作了 Writer
w.Write(...)
}
}
Struct Tags
Struct field 可以加 tag,給 reflection / encoding 套件用。
基本語法
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email,omitempty"`
Pass string `json:"-"`
}
Tag 是 ` 包起來的字串,內部慣例是 key:"value" 空格分隔。
JSON Tag 規則
type User struct {
ID int `json:"id"` // 改 key 名
Name string `json:"name"`
Email string `json:"email,omitempty"` // 空值省略
Pass string `json:"-"` // 完全不輸出
Roles []string `json:"roles,omitempty"`
}
// Marshal
u := User{ID: 1, Name: "Alice", Email: "", Pass: "secret", Roles: nil}
b, _ := json.Marshal(u)
// {"id":1,"name":"Alice"} // email/roles omitempty 省略,pass `-` 不輸出
常見 Tag
| 套件 | Tag key | 範例 |
|---|---|---|
encoding/json |
json |
json:"id,omitempty" |
encoding/xml |
xml |
xml:"id,attr" |
gorm |
gorm |
gorm:"primaryKey" |
validator |
validate |
validate:"required,email" |
| 自訂 | 自訂 | myapp:"secret" |
多個 tag 並存
type User struct {
Email string `json:"email" validate:"required,email" gorm:"uniqueIndex"`
}
用 reflect 讀 tag
import "reflect"
type User struct {
Email string `json:"email" validate:"required"`
}
func main() {
u := User{}
t := reflect.TypeOf(u)
f, _ := t.FieldByName("Email")
fmt.Println(f.Tag.Get("json")) // email
fmt.Println(f.Tag.Get("validate")) // required
}
實戰範例
範例 1:用 interface 抽象資料存取
package main
import (
"errors"
"fmt"
)
type User struct {
ID int
Name string
}
// Repository 抽象介面
type UserRepository interface {
Get(id int) (*User, error)
Save(u *User) error
}
// 記憶體實作
type MemoryRepo struct {
users map[int]*User
}
func NewMemoryRepo() *MemoryRepo {
return &MemoryRepo{users: make(map[int]*User)}
}
func (r *MemoryRepo) Get(id int) (*User, error) {
u, ok := r.users[id]
if !ok {
return nil, errors.New("not found")
}
return u, nil
}
func (r *MemoryRepo) Save(u *User) error {
r.users[u.ID] = u
return nil
}
// 業務層只依賴介面
type UserService struct {
repo UserRepository
}
func (s *UserService) GetName(id int) (string, error) {
u, err := s.repo.Get(id)
if err != nil {
return "", err
}
return u.Name, nil
}
func main() {
repo := NewMemoryRepo()
repo.Save(&User{ID: 1, Name: "Alice"})
svc := &UserService{repo: repo}
name, _ := svc.GetName(1)
fmt.Println(name) // Alice
}
之後想換 DB 實作,只要新增 type PostgresRepo struct{...} 並實作介面,業務層不用改。
範例 2:用 embedding 組合行為
type Logger struct {
Prefix string
}
func (l Logger) Log(msg string) {
fmt.Printf("[%s] %s\n", l.Prefix, msg)
}
type Server struct {
Logger // 嵌入
Addr string
}
func main() {
s := Server{
Logger: Logger{Prefix: "SRV"},
Addr: ":8080",
}
s.Log("starting") // [SRV] starting(直接用嵌入的 method)
}
範例 3:用 type switch 處理多型別輸入
func describe(input any) string {
switch v := input.(type) {
case nil:
return "<nil>"
case bool:
if v {
return "yes"
}
return "no"
case int, int64:
return fmt.Sprintf("%d", v)
case float64:
return fmt.Sprintf("%.2f", v)
case string:
return v
case []byte:
return string(v)
case fmt.Stringer:
return v.String()
default:
return fmt.Sprintf("unknown: %T", v)
}
}
範例 4:JSON 雙向轉換 + omitempty
type Config struct {
Host string `json:"host"`
Port int `json:"port"`
Debug bool `json:"debug,omitempty"`
Secret string `json:"-"`
}
// Marshal
c := Config{Host: "localhost", Port: 8080, Debug: false, Secret: "shhh"}
b, _ := json.Marshal(c)
// {"host":"localhost","port":8080} // debug=false omit,secret 永不輸出
// Unmarshal
input := []byte(`{"host":"example.com","port":443,"debug":true}`)
var c2 Config
json.Unmarshal(input, &c2)
fmt.Printf("%+v\n", c2)
// {Host:example.com Port:443 Debug:true Secret:}
最佳實踐
1. 介面定義在「使用方」而非「實作方」
// ❌ 在 db 套件定義介面
package db
type UserRepo interface { Get(int) *User }
type postgresRepo struct{}
// ✅ 在 service 套件定義介面(用什麼定義什麼)
package service
type UserRepo interface { Get(int) *User } // service 需要的
理由:介面應該描述「使用者需要什麼」,由使用方定義。實作方只要符合即可。這個原則叫 Accept interfaces, return structs。
2. 介面要小
// ✅ 標準庫範例:io.Reader 只有一個 method
type Reader interface {
Read(p []byte) (n int, err error)
}
// ❌ 大而全的介面難實作、難測試
type UserManager interface {
Get(id int) *User
Create(u *User) error
Update(u *User) error
Delete(id int) error
List() []*User
Count() int
// ...
}
理由:小介面組合自由,大介面強耦合。
3. Pointer receiver 用一致
// ✅ 整個型別都用同一種
func (u *User) Validate() error { ... }
func (u *User) Save() error { ... }
// ⚠️ 混用容易出錯
func (u User) Validate() error { ... } // value
func (u *User) Save() error { ... } // pointer
4. 自訂型別包裝原型別防混淆
// ✅ 強型別
type UserID int
type OrderID int
func getUser(id UserID) {} // 不能誤傳 OrderID
// ❌ 都是 int 容易傳錯
func getUser(id int) {}
5. Embed 用在「is-a」關係
// ✅ Dog is-a Animal
type Dog struct { Animal }
// ❌ Server has-a Logger,不是 is-a
type Server struct { Logger } // 改用 field
type Server struct {
logger Logger
}
但如果 Server 真的「就是 Logger」(要在外部呼叫 server.Log),embedding 也合理。判斷:你想讓 server.Log() 可被外部呼叫嗎?
6. Struct tag 統一風格
// ✅ 一致 lowercase + omitempty
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email,omitempty"`
}
// ❌ 混雜大小寫
type User struct {
ID int `json:"ID"`
Name string `json:"Name"`
}
常見問題
問題 1:介面實作沒滿足卻沒編譯錯
症狀:以為某型別實作了介面,呼叫時 runtime panic
檢查:用編譯時斷言
var _ Shape = (*Rectangle)(nil) // 編譯期檢查:Rectangle 是否實作 Shape
// 如果沒實作,這行編譯失敗
放在套件層級,建議所有對外型別都這樣斷言。
問題 2:方法集差異(value vs pointer)
type T struct{}
func (t T) M1() {}
func (t *T) M2() {}
var v T = T{}
v.M1() // OK
v.M2() // OK(自動取 &v)
var p *T = &T{}
p.M1() // OK
p.M2() // OK
// 介面實作
type I1 interface { M1() }
type I2 interface { M1(); M2() }
var i1 I1 = T{} // OK
var i2 I2 = T{} // ❌ T 沒有 pointer receiver 的 M2 在 value 方法集
var i2 I2 = &T{} // OK
規則:
- Value
T的方法集 = 所有(t T)receiver 的 method - Pointer
*T的方法集 = 所有 method(含 value 與 pointer receiver)
問題 3:JSON Unmarshal 後欄位是 zero value
檢查:
- 欄位必須首字母大寫(exported)才會被 JSON 處理
- JSON tag 與實際 key 不符
// ❌ 小寫無法 unmarshal
type User struct {
name string `json:"name"` // 不會被填值
}
// ✅
type User struct {
Name string `json:"name"`
}
問題 4:embedded 欄位有同名 → 編譯錯
type A struct { X int }
type B struct { X int }
type C struct {
A
B
}
c := C{}
c.X // ❌ ambiguous(A.X 還是 B.X?)
c.A.X // ✅ 明確
c.B.X // ✅ 明確
問題 5:interface 比較 panic
type T struct { Data []int }
var a, b any = T{Data: []int{1}}, T{Data: []int{1}}
a == b // panic: comparing uncomparable type T
原因:T 含 slice,不可比較。介面 == 會 panic。
解決:用 reflect.DeepEqual(a, b)。
問題 6:interface 的 nil 又出現了
type MyError struct{}
func (e *MyError) Error() string { return "..." }
func foo() error {
var e *MyError
return e // 介面值非 nil!
}
if foo() != nil {
// 進來這裡,雖然 e 是 nil
}
規則:要回傳 nil error 就明確 return nil,別 return 具名 nil pointer。
總結
核心要點
型別 = struct + method + interface
介面是結構型(隱式實作)+ 小而組合
embedding 取代繼承,做 method 提升 + 欄位透傳
type assertion / switch 從 interface 取出實際型別
struct tag 給 encoding/reflection 用
設計原則:
- ✅ 介面定義在使用方,小而精
- ✅ Pointer receiver 用一致(建議全部 pointer)
- ✅ 自訂型別包裝防型別混淆
- ✅ Embed 用在「is-a」關係
- ✅ 對外型別加編譯時斷言
速查表
// 自訂型別
type ID int
type User struct { ID; Name string }
func (u *User) Save() error { ... }
// 介面
type Saver interface {
Save() error
}
// 編譯時斷言
var _ Saver = (*User)(nil)
// Type assertion
v, ok := x.(*User)
// Type switch
switch v := x.(type) {
case *User: ...
case int: ...
}
// Embedding
type Admin struct { User; Level int }
// Struct tag
type T struct {
F int `json:"f,omitempty" validate:"required"`
}
Receiver 速查
| 場景 | 用 |
|---|---|
| 需要修改 receiver | Pointer |
| 大 struct(>16 bytes) | Pointer |
| 含 sync.Mutex 等不可拷貝 | Pointer |
| 一致性(推薦) | 全部 Pointer |
| 小 immutable wrapper | Value 可接受 |
相關閱讀
- Go 錯誤處理 — error 介面實作、自訂 error 型別
- Go 指標完全指南 — Pointer receiver 的
*與&用法 - Go Generics — 型別參數補足 interface 的不足
- Go 標準庫實戰 — encoding/json 配合 struct tag
參考資源
- Effective Go:https://go.dev/doc/effective_go
- Go Tour - Methods and Interfaces:https://go.dev/tour/methods/1
- Go FAQ - Interfaces:https://go.dev/doc/faq#types
- Dave Cheney - SOLID Go Design:https://dave.cheney.net/2016/08/20/solid-go-design
建立日期:2026-05-16 最後更新:2026-05-16