議程3 - Reflect 用太多,都不好意思跟別人說 Go 靜態了 - 高宜誠 / YC Kao

tags: GopherDay2024 Agenda
HackMD Error: 403 error

Slido 連結

投影片連結

Why Reflect?

主要為動態語言與靜態語言差異:

  • runtime type check vs compile time type check
  • flexibility vs safety

天有不測風雲,人有 JSON YAML YC. 2024

What is reflect?

靜態的處理動態內容
可以做到

  • read type information: 如 json tag
  • dynamic modify value and types
graph TB
    subgraph Go Memory
        XXXStruct["type XXX struct {...}"]
        Value["reflect.Value"]
        Type["reflect.Type"]
    end

    XXXStruct --> |指向| Value
    XXXStruct --> |指向| Type

    Value --> |包含資料的實例| XXXStruct
    Type --> |包含型別的描述| XXXStruct


How reflect works

ABI (Application Binary Interface)

  • 紀錄每個 type 的 memory layout

  • reflect rely on ABI

  • reflect.Type
    紀錄該型別的描述,例如是否 comparable
    從 abi 透過 unsafe.Pinter() 硬轉 rtype(具有所有資訊),並再轉 reflect.Type(具有 exposed 資訊)

例如是指標 type,可以拿到他指向的 type

StructField: 代表一個 struct 裡的一個 field,包含 field name, type, tag 等資訊

  • 也有 Anonymous 代表是否為 embedded field
    fun fact: 可以將 type C struct 放進其他 struct{A C} 並修改 Anonymous,達到名稱與型別不同的 embedded field

用 StructOf 可以拿多個 StructField,轉換成一個 type

reflect.Value 沒有 expose 任何 field,所有操作都要透過 reflect package,例如 reflect.Value.CanInt()

  • 因為不知道這個值裡面會有什欄位
  • 需要用 FieldByName or FieldByIndex
  • 在拿 FieldByName 時,若沒有 embed field,會提早結束,反之,會針對 embed filed 的 type 一層一層往下 reflect lookup。

小技巧

  1. 如果 type 沒有 embed,在做 reflect.Type.FieldByName() 會快很多 (差異十倍)
  2. 如果已知 field index,用 reflect.Type.FieldByIndex() 會避免陷入 deep dive 的問題
  3. 可以把 known fields 包成一個 struct 包進另一個 struct,這樣用 v.FieldByName("Known").Interface().(Known) 可以快速拿到已知 struct 的 field value (差異五倍)

// 求 source code
https://cs.opensource.google/go/go/+/refs/tags/go1.22.3:src/reflect/value.go;l=310

Q&A

  • 可在需要動態定義型別時使用reflect
  • reflect相對較不安全, 需要多寫test case檢查, 並多利用Can相關方法事先檢查, 減少panic的機會; 另外需要做好recover

reflect 從 abi 拿完整 memory layout 等等,也會被 GC 正常管理記憶體,相較直接使用 unsafe 來的安全(?)

Select a repo