Go 的泛型限制真是充满各种枷锁

标签:Go

最近想用 Go 写一个 Web 服务,于是看了下当前的 Web 框架性能排行榜
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 种方法都不行:
  1. 定义成 ...*P 这种不定参数的类型:然而泛型参数不支持。
  2. 传入 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条评论 你不来一发么↓

    想说点什么呢?