Go 1.18 引入泛型后,最实用的场景之一就是把”返回值 + 错误码”这种固定结构的接口统一起来。本文用一个小例子讲清楚约束怎么写、什么时候该用。
问题:重复的返回结构
每个业务接口都要返回”数据 + 状态码 + 消息”,手写三遍很烦:
type UserResp struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data *User `json:"data"`
}
type OrderResp struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data *Order `json:"data"`
}
字段一模一样,只是 Data 类型不同——这正是泛型的用武之地。
定义通用的 Result
type Result[T any] struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data T `json:"data"`
}
T any 表示任意类型。用法:
func OK[T any](data T) Result[T] {
return Result[T]{Code: 0, Msg: "success", Data: data}
}
func Fail(code int, msg string) Result[any] {
return Result[any]{Code: code, Msg: msg}
}
调用时类型由实参推断,不需要显式写类型参数:
r := OK(&User{Name: "tom"}) // Result[*User]
r2 := OK([]Order{}) // Result[[]Order]
加约束:只允许可比较类型
如果要做缓存,key 必须可比较。用约束限定类型:
type Cache[K comparable, V any] struct {
m map[K]V
mu sync.RWMutex
}
func (c *Cache[K, V]) Get(k K) (V, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
v, ok := c.m[k]
return v, ok
}
comparable 是预声明约束,只支持 == 和 !=。注意切片和 map 不能做 key,编译期就会报错。
自定义约束
想限制”只能是实现了 String 方法的类型”:
type Stringer interface {
String() string
}
func Join[T Stringer](items []T, sep string) string {
parts := make([]string, len(items))
for i, it := range items {
parts[i] = it.String()
}
return strings.Join(parts, sep)
}
多个约束用 | 联合:
type Number interface {
~int | ~int64 | ~float64
}
~ 表示”包含以该类型为底层类型的所有类型”,比如 type UserID int 也能通过。
什么时候别用泛型
- 只有一种类型会用 → 直接写具体类型,可读性更好
- 类型差异只在某个字段 → 考虑用接口而不是泛型
- 为了”显得高级” → 泛型会增加编译时间和代码复杂度
泛型的价值在于消除重复的类型声明,不是为了炫技。上面 Result[T] 省掉了十几个几乎一样的结构体,这才是它该出现的地方。