【问题标题】:How can I make type assertions against an anonymous function?如何对匿名函数进行类型断言?
【发布时间】:2021-12-04 21:09:53
【问题描述】:

我正在使用 Gorilla 在 Go 中编写 HTTP 服务。我是 Go 新手(

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

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. 为什么命名和未命名类型的语义不同?

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

【问题讨论】:

    标签: go gorilla


    【解决方案1】:

    http.HandlerFunc 是一种特定类型。 Ping 函数不属于该类型,尽管它可以转换为该类型,因为它们都具有相同的基础类型。

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

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

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

    • 如果T不是接口类型,x.(T)断言x的动态类型是identicalT类型
    • 如果T是一个接口类型,x.(T)断言x的动态类型实现了接口T

    【讨论】:

    • 谢谢。我不清楚的是匿名函数的类型不(就编译器可以检测到的)隐式匹配HandlerFunc。让我有点困惑的是,整个“鸭子打字”事情适用于接口但不适用于所有类型,而且我可以将匿名函数 castHandlerFunc 也令人困惑,即使类型断言不起作用。
    • @JakeRobb 我用语言规范中的一些内容更新了答案,这可能会解决这些问题。
    • 我仍然对为什么语言设计者选择让语义转换和类型断言不同的原因感到困惑。特别是,为什么名称的存在会改变这些语义?我觉得它非常不直观。并不是说我希望语言设计者在这里回答,但也许有人知道在哪里可以找到语言邮件列表或其他东西上的相关对话......
    • @JakeRobb 如果它让您感到困扰,您可以前往go-nuts 邮件列表并在那里询问,语言设计者和贡献者确实会访问,有时甚至会参与。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-05-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多