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

How can I make type assertions against an anonymous function?

我正在使用 Gorilla 在 Go 中编写 HTTP 服务。我是 Go 的新手(<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 
    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 and/or ApplyWrappers 中)可以恢复将命名 and/or 匿名函数传递给的能力注册处理程序?

编辑:非常感谢您的快速回答。问题已解决:

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 
    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

  • if T is not an interface type, x.(T) asserts that the dynamic type of x is identical to the type T
  • If T is an interface type, x.(T) asserts that the dynamic type of x implements the interface T.