假如咱們有這樣一個包:iface.gogit
package iface func GetAddFunc() interface{} { return add } type i32 int32 func add(a, b i32) i32 { return a + b }
但願能夠在包外執行add函數,怎麼辦?此處,由於該函數簽名是不可導出的,因此,正常思路是使用反射,代碼多是這樣:github
import ( "fmt" "iface" "reflect" ) func main() { addIface := iface.GetAddFunc() ret := reflect.ValueOf(addIface).Call([]reflect.Value{ reflect.ValueOf(1), reflect.ValueOf(2), })[0].Int() fmt.Println(ret) // expect: 3 }
可是,運行時發現 panic 了?!錯誤信息以下:函數
panic: reflect: Call using int as type iface.i32
這可咋辦?使用斷言?類型不可導出,更加不可能! 這時,unsafe的高階用法就派上用場了。先上最終代碼,再來分析實現思路:性能
import ( "fmt" "iface" "unsafe" "github.com/henrylee2cn/goutil/tpack" ) func main() { addIface := iface.GetAddFunc() ptr := tpack.Unpack(addIface).Pointer() add := *(*func(a, b int32) int32)(unsafe.Pointer(&ptr)) fmt.Println(add(1, 2)) // Output: 3 }
該代碼,使用了本人開源的unsafe進階包tpack。 它能夠幫助咱們拿到函數的Pointer,以後咱們就能經過unsafe強轉成外部定義的等價類型func(a, b int32) int32
,進而執行它。ui
這種方法不但適用範圍普遍,並且執行時沒有任何性能損耗(等價於直接調用iface.add
)指針
那麼,讓咱們瞭解一下tpack.Unpack(iface.GetAddFunc()).Pointer()
這行代碼作了什麼事情呢?code
首先,addIface
是接口類型,而接口類型的底層類型原型以下:接口
emptyInterface struct { typ *rtype word unsafe.Pointer }
typ字段存儲類型信息,word字段存儲指向數據的位置信息。咱們當前需求只關心word。經過偏移量,咱們能夠拿到word的值,進而拿到函數在內存中的起始位置,即Pointer。內存
ptrOffset := unsafe.Offsetof(new(emptyInterface).word)
word := uintptr(unsafe.Pointer(&addIface)) + ptrOffset
ptr := *(*uintptr)(unsafe.Pointer(word))
ptr
表示函數的起始位置,而unsafe.Pointer要求是變量的指針,所以,須要使用 &ptr
進行指針類型的強轉:add := *(*func(a, b int32) int32)(unsafe.Pointer(&ptr))
更多有趣的unsafe進階操做,能夠了解 tpackget
個人博客即將同步至騰訊雲+社區,邀請你們一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=2pkhijryroysk