我正在实现一个加载 go 插件的 go 模块。
我假设主包上存在具有特定名称和特定签名的函数,并且希望在找不到或与预期签名不匹配的情况下有一个很好的错误消息。
给定一个具有函数类型的变量,如何获得该函数的底层签名?
以下仅打印类型的名称(例如main.ModuleInitFunc)而不是完整签名。
package main
import "fmt"
type ModuleInitFunc func(someInt int) error
func main() {
var myFunc ModuleInitFunc = nil
fmt.Printf("%T", lol)
}
Run Code Online (Sandbox Code Playgroud)
reflect.Type.String()只返回类型名称,因此如果函数值具有命名类型,您将只能看到类型名称。请注意,如果函数值是函数文字(具有未命名的类型),这将打印函数的签名:
var myFunc ModuleInitFunc
fmt.Printf("%T\n", myFunc)
fmt.Printf("%T\n", func(i int) error { return nil })
Run Code Online (Sandbox Code Playgroud)
输出(在Go Playground上试试):
main.ModuleInitFunc
func(int) error
Run Code Online (Sandbox Code Playgroud)
如果类型是命名类型,我们必须自己构造签名,但幸运的是,它reflect.Type具有我们需要的所有信息。
Type.In()返回第 i个参数Type.Out()的类型,同样返回第 i个结果类型的类型。
使用这些,这是一个返回函数值签名的示例实现:
func signature(f interface{}) string {
t := reflect.TypeOf(f)
if t.Kind() != reflect.Func {
return "<not a function>"
}
buf := strings.Builder{}
buf.WriteString("func (")
for i := 0; i < t.NumIn(); i++ {
if i > 0 {
buf.WriteString(", ")
}
buf.WriteString(t.In(i).String())
}
buf.WriteString(")")
if numOut := t.NumOut(); numOut > 0 {
if numOut > 1 {
buf.WriteString(" (")
} else {
buf.WriteString(" ")
}
for i := 0; i < t.NumOut(); i++ {
if i > 0 {
buf.WriteString(", ")
}
buf.WriteString(t.Out(i).String())
}
if numOut > 1 {
buf.WriteString(")")
}
}
return buf.String()
}
Run Code Online (Sandbox Code Playgroud)
测试它:
var myFunc ModuleInitFunc
fmt.Println(signature(func(i int) error { return nil }))
fmt.Println(signature(myFunc))
fmt.Println(signature(time.Now))
fmt.Println(signature(os.Open))
fmt.Println(signature(log.New))
fmt.Println(signature(""))
Run Code Online (Sandbox Code Playgroud)
输出(在Go Playground上试试):
func (int) error
func (int) error
func () time.Time
func (string) (*os.File, error)
func (io.Writer, string, int) *log.Logger
<not a function>
Run Code Online (Sandbox Code Playgroud)
请注意,不能同时打印参数和结果类型的名称,因为它们无法存储/访问。有关详细信息,请参阅Go 中的未命名参数是一回事吗?