Go 泛型实战:5 个典型使用案例,让你彻底掌握泛型威力

作者:一位热爱 Go 的开发者
适用版本:Go 1.18+

自从 Go 1.18 引入泛型(Generics)以来,社区对它的讨论从未停止。有人认为“终于来了”,也有人觉得“Go 不需要泛型”。但不可否认的是:泛型确实让 Go 在表达通用逻辑时更简洁、安全、高效

本文不讲理论堆砌,而是通过 5 个真实使用场景,带你亲手写出泛型代码,彻底掌握泛型的核心价值。


1️⃣ 通用工具函数:不再重复写 Min / Max / Filter

问题:以前在 Go 中,如果你想写一个 Min 函数,就得为 intfloat64string 各写一遍:

1
2
func MinInt(a, b int) int { /*...*/ }
func MinFloat(a, b float64) float64 { /*...*/ }

泛型方案

1
2
3
4
5
6
func Min[T constraints.Ordered](a, b T) T {
if a < b {
return a
}
return b
}

constraints.Ordered 是 Go 内置的类型约束,包含所有可比较大小的类型(int、float、string 等)。

使用

1
2
fmt.Println(Min(3, 5))        // 3
fmt.Println(Min("apple", "banana")) // "apple"

同理,你也可以写出泛型版的 FilterMapContains 等高阶函数,告别重复代码。


2️⃣ 泛型数据结构:安全又灵活的 Stack / Queue

问题:以前用 interface{} 实现栈,类型不安全,使用时要断言:

1
2
stack := []interface{}{"hello", 42}
item := stack[0].(string) // 危险!如果类型错了就 panic

泛型方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type Stack[T any] struct {
data []T
}

func (s *Stack[T]) Push(v T) {
s.data = append(s.data, v)
}

func (s *Stack[T]) Pop() (T, bool) {
if len(s.data) == 0 {
var zero T
return zero, false
}
top := s.data[len(s.data)-1]
s.data = s.data[:len(s.data)-1]
return top, true
}

使用

1
2
3
4
5
6
s := &Stack[string]{}
s.Push("Go")
s.Push("泛型")
if v, ok := s.Pop(); ok {
fmt.Println(v) // "泛型"
}

✅ 类型安全!编译期就能检查错误,无需运行时断言。


3️⃣ 统一 API 响应结构:泛型包裹业务数据

问题:很多 Web API 都返回类似结构:

1
{ "code": 200, "message": "OK", "data": {...} }

以前你可能用 map[string]interface{} 或重复定义多个 ResponseXXX 结构体。

泛型方案

1
2
3
4
5
type Response[T any] struct {
Code int `json:"code"`
Message string `json:"message"`
Data T `json:"data"`
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
type User struct { Name string }

resp := Response[User]{
Code: 200,
Message: "success",
Data: User{Name: "Alice"},
}

// JSON 输出自动包含正确类型
b, _ := json.Marshal(resp)
fmt.Println(string(b))
// {"code":200,"message":"success","data":{"Name":"Alice"}}

✅ 一个 Response[T] 搞定所有接口,强类型 + 自动生成文档(配合 Swagger 更香)。


4️⃣ 泛型缓存:适配任意 key-value 类型

问题:你想写一个内存缓存,但 key 和 value 的类型不确定。

泛型方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type Cache[K comparable, V any] struct {
data map[K]V
mu sync.RWMutex
}

func NewCache[K comparable, V any]() *Cache[K, V] {
return &Cache[K, V]{ make(map[K]V)}
}

func (c *Cache[K, V]) Set(k K, v V) {
c.mu.Lock()
defer c.mu.Unlock()
c.data[k] = v
}

func (c *Cache[K, V]) Get(k K) (V, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
v, ok := c.data[k]
return v, ok
}

使用

1
2
3
4
5
6
// 缓存 int -> string
cache := NewCache[int, string]()
cache.Set(1, "Go")
if v, ok := cache.Get(1); ok {
fmt.Println(v) // "Go"
}

comparable 是 Go 泛型的关键约束:只有可作为 map key 的类型(如 int、string、struct 无 slice 等)才能用于 K。


5️⃣ 泛型中间件:复用逻辑,不牺牲类型

在 Web 框架(如 Gin、Echo)中,你可能想写一个通用日志中间件,但又想保留 handler 的具体类型。

虽然框架本身可能不完全支持泛型 handler,但你可以在业务层利用泛型封装:

1
2
3
4
5
6
7
8
9
10
type HandlerFunc[T any] func(c *Context, req T) (T, error)

func WithLogging[T any](h HandlerFunc[T]) HandlerFunc[T] {
return func(c *Context, req T) (T, error) {
start := time.Now()
resp, err := h(c, req)
log.Printf("处理耗时: %v", time.Since(start))
return resp, err
}
}

这种模式在构建 RPC、CLI 工具或内部 SDK 时非常有用,逻辑复用 + 类型安全 一举两得。


✅ 总结:Go 泛型不是“银弹”,但它是“瑞士军刀”

场景 是否推荐用泛型
工具函数(Min/Max/Filter) ✅ 强烈推荐
数据结构(Stack/Queue/Map) ✅ 推荐
API 响应/请求封装 ✅ 推荐
高性能 hot path 代码 ⚠️ 谨慎(避免过度抽象)
简单业务逻辑 ❌ 不需要,直接写更清晰

**记住:泛型的目标不是“炫技”,而是 ——

减少重复、提升安全、增强表达力。**


📚 延伸阅读

  • Go 泛型官方教程
  • golang.org/x/exp/constraints(常用约束集合)
  • 《Go 泛型设计哲学》by Robert Griesemer

你用 Go 泛型写过哪些实用代码?欢迎在评论区分享!
如果你觉得这篇文章帮你理清了泛型的使用思路,别忘了 点赞 + 转发,让更多 Go 开发者受益!


🐹 小贴士:本文代码均可在 Go Playground 运行(需 Go 1.18+)。本地开发推荐使用 Go 1.22+ 以获得最佳泛型支持。