Go 的泛型限制真是充满各种枷锁
2025 4 11 05:16 PM 20次查看
Go 框架中,排第一的是 Fast HTTP,但是文档几乎没有,看了下如果要获取一个 query 参数,大概要这样写:
ctx.QueryArgs().Peek("foo")
。紧随其后的是 atreugo,写法也是类似:ctx.UserValue("foo").(string)
。都 2025 年了,连 Python 都不干这种硬编码字符串和手动类型转换的事了,没想到 Go 还在做。
直到我继续翻到第 7 位的 GoFrame,才终于看到了类似 FastAPI 的实现:
type HelloReq struct {
g.Meta `path:"/" method:"get"`
Name string `v:"required" dc:"姓名"`
Age int `v:"required" dc:"年龄"`
}
type HelloRes struct{}
type Hello struct{}
func (Hello) Say(ctx context.Context, req *HelloReq) (res *HelloRes, err error) {
r := g.RequestFromCtx(ctx)
r.Response.Writef(
"Hello %s! Your Age is %d",
req.Name,
req.Age,
)
return
}
func main() {
s := g.Server()
s.Group("/", func(group *ghttp.RouterGroup) {
group.Bind(
new(Hello),
)
})
s.SetPort(8000)
s.Run()
}
可是我只是想定义参数类型,却不得不定义 Hello
这个结构体,还要搞个 group 来 bind,感觉有点封装过度了。于是我参考了一下它的实现,准备基于 fasthttp/router 来实现一个路由。
初版很简单,只是验证想法:
type Router struct {
Router *fasthttprouter.Router
}
func NewRouter() *Router {
return &Router{
Router: fasthttprouter.New(),
}
}
type RequestHandler func(ctx *fasthttp.RequestCtx, params interface{})
func (r *Router) Handle(method, path string, handler RequestHandler) {
// 这里都是反射的代码,我直接让 AI 生成的,可以不用关注
handlerType := reflect.TypeOf(handler)
if handlerType.NumIn() != 2 {
panic("RequestHandler must have 2 arguments")
}
r.Router.Handle(method, path, func(ctx *fasthttp.RequestCtx) {
hVal := reflect.ValueOf(handler)
hType := hVal.Type()
paramsType := hType.In(1)
if paramsType.Kind() != reflect.Ptr || paramsType.Elem().Kind() != reflect.Struct {
handler(ctx, nil)
return
}
paramsValue := reflect.New(paramsType.Elem())
err := bindQueryParams(ctx, paramsValue.Interface())
if err != nil {
ctx.SetStatusCode(fasthttp.StatusBadRequest)
ctx.WriteString("Error binding query parameters: " + err.Error())
return
}
handler(ctx, paramsValue.Interface())
})
}
但是 params interface{}
有点不够静态,需要做动态检查。于是我又改成了这样:
type RequestHandler[P any] func(ctx *fasthttp.RequestCtx, params *P)
稍好一点,但是并没有 any struct 这种东西。而且,Go 不支持泛型方法的问题也出现了,只能改成泛型函数:
func Handle[P any](r *Router, method, path string, handler RequestHandler[P]) {
var zero P
paramType := reflect.TypeOf(zero)
handlerValue := reflect.ValueOf(handler)
// 以下省略
}
然后又一想,一些简单的接口可能是没有参数的,能不能省略
params
参数让它跳过反射这步呢?很遗憾,我尝试了 2 种方法都不行:
- 定义成
...*P
这种不定参数的类型:然而泛型参数不支持。 - 传入
nil
来表示:实参可以用nil
,但函数定义时,形参类型不能声明成nil
,还是得引入一个空的*struct{}
,不太优雅。
既然没办法,那就忍忍吧。可是大量的
Handle()
注册显得很繁琐,能不能接收一个 slice,一次性注册所有接口呢?当然可以,只是仍然不静态:
type Route struct {
Method string
Path string
Handler interface{} // Can be fasthttp.RequestHandler or RequestHandler[P]
}
func (r *Router) Handles(routes []Route) {
// 省略反射判断 Handler 类型的部分
}
明明 Go 推出类型的组合就是为了泛型:
type Int32 interface {
int32 | uint32
}
但是却不支持泛型类型的组合:type Handler interface {
fasthttp.RequestHandler | RequestHandler[P]
}
写到这里我已经想放弃了,这到底是算泛型,还是算反射啊?
0条评论 你不来一发么↓