Go 型別與介面完全指南

Go 型別系統與介面完整指南 — struct、method、interface、embedding、type assertion、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。

詳見 錯誤處理 → nil error 比較失敗


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

檢查

  1. 欄位必須首字母大寫(exported)才會被 JSON 處理
  2. 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 可接受

相關閱讀


參考資源


建立日期:2026-05-16 最後更新:2026-05-16

🔗相關文章