泛型函数应避免使用any,而需根据实际操作需求定义精准约束:如comparable、Numeric、fmt.Stringer等;~int支持类型别名,而int不支持;业务专属约束(如EntityID)比通用接口更早暴露错误,提升类型安全。

泛型函数里用 any 就等于放弃类型安全
直接写 func Process[T any](v T) 看似灵活,实则埋雷。编译器不会阻止你把 []byte 传给一个内部调用 json.Unmarshal 的函数,只要它没显式要求 io.Reader 或 string —— 错误会拖到运行时才暴露。
真正起作用的约束必须反映实际操作需求:
- 需要比较大小?用
comparable或自定义Ordered接口(如~int | ~string) - 需要加减乘除?定义
Numeric接口,明确列出支持的数值类型 - 需要调用
.String()?约束为fmt.Stringer,而不是any
~int 和 int 在约束里区别很大
int 是具体类型,~int 表示“底层类型为 int 的所有类型”,比如你定义的 type UserID int,它能匹配 ~int,但不满足纯 int 约束。
业务中更常见的是后者——ID、Amount、Version 这类别名类型必须被接纳:
立即学习“go语言免费学习笔记(深入)”;
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 错误写法:
type ID interface{ int }→UserID不满足 - 正确写法:
type ID interface{ ~int | ~string }→UserID和OrderID string都能过 - 注意:
~只能用于基本类型,不能写~MyStruct
嵌套泛型参数容易触发编译器报错
像 func Transform[T any, U any](data []T, f func(T) U) []U 看起来通用,但一旦 f 内部调用非泛型函数(比如旧版 sql.NullInt64.Scan),类型流就断了,错误信息常是 cannot use T as int in argument,而不是你预期的逻辑问题。
更稳妥的做法是收窄第一层约束:
- 如果
f只处理数字,就把T约束为Numeric,而不是any - 避免在泛型函数里调用未泛型化的第三方库方法,尤其涉及反射或
interface{}的 - Go 1.26 仍不支持方法上再声明类型参数,所以
func (s *Svc[T]) Do[R any]()是非法的
业务专属约束比通用接口更早暴露误用
项目里定义 type EntityID interface{ ~int64 | ~string },再写 FindByID[T EntityID](id T),比用 [T any] + 运行时校验强得多。编译阶段就能卡住 FindByID(3.14) 这种调用。
这种约束不是为了炫技,而是把业务语义编译进类型系统:
- 数据库主键、API 路径参数、缓存 key 这些地方,
EntityID比any更贴近真实契约 - 别把
golang.org/x/exp/constraints当银弹——它提供的Integer、Float是通用兜底,业务 ID 应该自己定义 - 约束越窄,后期重构越轻松;
any开头的函数,最后往往要重写三遍才能收敛
类型约束不是语法装饰,它是你在写函数签名时,对调用者做出的最小可行承诺。承诺太宽,大家不敢用;承诺太窄,自己绕不开。平衡点就在业务动词和名词的交界处。

















