如何对匿名函数进行类型断言?

  • 本文关键字:类型 断言 函数 go gorilla
  • 更新时间 :
  • 英文 :


我正在使用大猩猩在Go中编写HTTP服务。我是新手(有1年的经验),但进步得相当快。

我有一个函数用来注册我的处理程序:

func (s *Server) RegisterHandler(path string, handler http.HandlerFunc, methods ...string) {
if len(methods) == 0 {
s.Router.Handle(path, handler).Methods(http.MethodGet)
} else {
s.Router.Handle(path, handler).Methods(methods...)
}
}

我有一些代码注册命名函数作为处理程序:

func (s *Server) RegisterDefaultHandlers() {
s.RegisterHandler("/ping", Ping)
}
func Ping(w http.ResponseWriter, request *http.Request) {
responders.RespondOk(w)
}

我也有单元测试代码注册匿名函数作为处理程序:

s.RegisterHandler("/testPath", func(w http.ResponseWriter, r *http.Request) {
// whatever the test calls for
}, http.MethodPost)

这一切都很好——只是建立我的起点。

今天,我发现自己对Go的类型系统产生了强烈的抵触。我正在定义一些自定义处理程序类型,例如:

type UserAwareHandlerFunc func(http.ResponseWriter, *http.Request, models.User)

我还引入了一个函数来允许我注册这样的处理程序,并将所有处理程序包装在context.ClearHandler中。如果这可以工作,我还将用另一个函数包装所有内容,该函数将在日志上下文中设置一些内容。到目前为止我有什么:

func (s *Server) RegisterHandler(path string, handler interface{}, methods ...string) {
wrappedHandler := wrappers.ApplyWrappers(handler)
if len(methods) == 0 {
s.Router.Handle(path, wrappedHandler).Methods(http.MethodGet)
} else {
s.Router.Handle(path, wrappedHandler).Methods(methods...)
}
}
func ApplyWrappers(handler interface{}) http.Handler {
var result http.Handler
if userAwareHandler, ok := handler.(UserAwareHandlerFunc); ok {
result = UserAware(userAwareHandler)
} else if handlerFunc, ok := handler.(http.HandlerFunc); ok {
result = handlerFunc
} else if handlerObj, ok := handler.(http.Handler); ok {
result = handlerObj
} else {
log.Fatalf("handler %+v (type %s) is not a recognized handler type.", handler, reflect.TypeOf(handler))
}
// to avoid memory leaks, ensure that all request data is cleared by the end of the request lifetime
// for all handlers -- see https://stackoverflow.com/a/48203334
result = context.ClearHandler(result)
return result
}
func UserAware(handler UserAwareHandlerFunc) http.Handler {
return func(w http.ResponseWriter, r *http.Request) {
user := ... // get user from session
handler(w, r, user)    
}
}

通过这些修改,我不能再注册命名函数或匿名函数;ApplyWrappers中的类型断言全部失败。我必须声明和定义一个类型变量,然后传入。

命名函数有两种可行的方法:

var Ping http.HandlerFunc = func(w http.ResponseWriter, request *http.Request) {
responders.RespondOk(w)
}
func Ping2(w http.ResponseWriter, request *http.Request) {
responders.RespondOk(w)
}
func (s *Server) RegisterDefaultHandlers() {
s.RegisterHandler("/ping", Ping)
var pingHandler2 http.HandlerFunc = Ping2
s.RegisterHandler("/ping2", pingHandler2)
}

对于匿名函数,我可以做:

var handler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
...
}
s.RegisterHandler("/testPath", handler, http.MethodPost)

我在这里构建的全部要点是将样板文件合并到一个地方,使我的许多测试和处理程序尽可能流线型。声明类型化变量的需要违背了这一目标。所以我的问题是:是否有一些特殊类型的魔法我可以使用(最好在RegisterHandler和/或ApplyWrappers),这将恢复将命名和/或匿名函数传递给RegisterHandler的能力?

编辑:非常感谢你的快速回答。问题解决:
func ApplyWrappers(handler interface{}) http.Handler {
var result http.Handler
if userAwareHandler, ok := handler.(UserAwareHandlerFunc); ok {
result = UserAware(userAwareHandler)
} else if anonymousFunc, ok := handler.(func(http.ResponseWriter,*http.Request)); ok {
result = http.HandlerFunc(anonymousFunc)
} else if handlerObj, ok := handler.(http.Handler); ok {
result = handlerObj
} else {
log.Fatalf("handler %+v (type %s) is not a recognized handler type.", handler, reflect.TypeOf(handler))
}
// to avoid memory leaks, ensure that all request data is cleared by the end of the request lifetime
// for all handlers -- see https://stackoverflow.com/a/48203334
result = context.ClearHandler(result)
return result
}

现在可以用了,但是我还有问题:

  1. 如果我理解正确的话,"鸭子打字"如果我处理的是接口而不是函数,那么我在这里寻找的行为就会很好。是什么驱动了这种区别?函数的鸭子类型是我可以合理地希望在语言的未来版本中看到的东西吗?
  2. 我可以匿名函数强制转换为HandlerFunc。对我来说,类型转换和类型断言不共享语义是很奇怪的。有人能解释一下吗?

编辑2:我现在已经看到了语言规范中说定义的类型(即有名称的类型)永远不会与任何其他类型相同,即使底层类型是相同的,因此永远不会在类型断言中工作(除非它是接口)。所以现在我想知道:

  1. 为什么在接口和其他类型之间的语义不同?
  2. 为什么对于命名和未命名类型的语义不同?

我发现两者都不直观,也不方便。我一个人在这儿吗?我想知道是否有人知道为什么在设计语言时做出这些决定(也许在不增加编译器或其输出等的情况下实现特别困难),以及是否有人知道解决这两种情况的计划。

http.HandlerFunc为特定类型。Ping函数不是该类型,尽管它可以转换为该类型,因为它们具有相同的底层类型。

如果handler是一个匿名函数,那么你需要在类型断言中使用匿名函数:

f, ok := handler.(func(http.ResponseWriter,*http.Request))

https://golang.org/ref/spec Type_assertions

  • 如果T不是接口类型,x.(T)断言x的动态类型与相同类型T
  • 如果T是接口类型,则x.(T)断言x的动态类型实现了T接口。

相关内容

  • 没有找到相关文章

最新更新