struct tag 與 reflect
struct tag 與 reflect
struct tag 是 Go 留給工具的後門。gortex 的宣告式路由就靠 reflect 讀這些 tag,把一個 struct 變成整張路由表。這篇先把地基打穩。
struct tag 怎麼讀
struct tag 是欄位後面那串 raw string,慣例是空白分隔的 key:"value":
type User struct {
Name string `json:"name" validate:"required"`
}
編譯器不解讀它,要靠 reflect 在執行期讀:
f, _ := reflect.TypeOf(User{}).FieldByName("Name")
f.Tag.Get("json") // "name"
v, ok := f.Tag.Lookup("xml") // "", false
Get 和 Lookup 的差別很關鍵:Get 對「沒這個 key」和「key 值為空」都回 "",分不出來;Lookup 多回一個 bool。gortex 判斷 inject:""(空值 tag)就是用 Lookup,否則會把「故意留空」誤判成「沒掛 tag」。
reflect 三件套:Type / Value / Kind
reflect.Type:靜態結構,有幾個欄位、欄位名、tag。reflect.Value:執行期的值,可讀可寫。Kind:底層分類(Ptr、Struct、String…),跟具名型別不同。type UserID int的 Kind 是Int。
v := reflect.ValueOf(&User{})
v.Kind() // reflect.Ptr
v.Elem().Kind() // reflect.Struct;Elem() 解一層指標
t := v.Elem().Type()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
_ = field.Tag.Get("json")
}
安全遍歷與方法簽章檢查
reflect 出錯不回 error,是直接 panic。動手前先檢查:
- 先驗
Kind:別對非 struct 呼叫NumField()、別對非指標呼叫Elem()。 - 跳過 unexported 欄位:
field.CanInterface()(或StructField.IsExported()),否則取值會 panic。 - 要呼叫方法前,先
MethodByName拿到,再看Method.Type的NumIn/NumOut確認簽章,才Call。
這正是 B2 路由註冊在做的事:掃欄位找 GET / POST 方法、檢查簽章、才註冊。
reflect 的代價
reflect 慢(繞過靜態分派)、繞過編譯期型別檢查、又會在執行期 panic。原則只有一條:在啟動/設定期用,別放進 hot path。gortex 就是這樣切:啟動時用 reflect 掃路由換開發體驗,production build 改用 codegen,每條請求完全不碰 reflect(見 B2、B4)。
Effective Go 精選:blank identifier、Data
blank identifier _ 在 Effective Go 有整節(The blank identifier):吃掉用不到的回傳值、for range 只要 index、以及介面檢查:
var _ io.Writer = (*MyType)(nil) // 編譯期確認 *MyType 有實作 io.Writer,沒有就編不過
Data 那節講配置:new(T) 配一塊歸零記憶體回 *T,make 專給 slice / map / channel。reflect.New 等價於 new,B2 自動初始化 nil handler 欄位就靠它。
重點整理
- struct tag 是欄位後的 raw string,reflect 用
Get/Lookup讀;Lookup能分辨「沒掛」與「掛了空值」。 - 三件套 Type / Value / Kind;Kind 看底層分類,跟具名型別無關。
- reflect 會 panic:先檢查 Kind、跳過 unexported、方法先驗簽章再 call。
- reflect 慢又不安全:只在啟動期用,請求路徑別碰。
- struct tag + reflect 就是 B2〈struct tag 宣告式路由〉(
gortex-struct-tag-routing)的地基。
延伸:本系列接著用這些觀念拆 gortex 框架,原始碼見 yshengliao/gortex。
大綱 Sheng,內文 Claude 協助 · 環境 Go 1.24(當時最新 release)· 本系列為事後回填整理 · 列入 20260613 blog 翻新計劃,新漆未乾。