【Go unsafe進階】執行空接口中的函數:除了斷言與反射,你還有更好的選擇

假如咱們有這樣一個包: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。內存

  • 計算word偏移量,在64位系統中,該值爲8:
ptrOffset := unsafe.Offsetof(new(emptyInterface).word)
  • 獲取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

相關文章
相關標籤/搜索