Go用泛型实现Result[T]可类型安全封装成功值与错误,避免interface{}的类型断言风险和错误忽略问题,核心在于通过泛型约束、构造函数封装及IsOk/Unwrap等方法强制显式错误处理。

Go 没有内置 Result 类型,但用泛型能干净地封装成功值和错误——关键不是“能不能做”,而是“怎么避免掉进类型擦除和错误传播的坑”。
为什么不用 interface{} 或 any 做 Result
很多人第一反应是写 type Result struct { Value any; Err error },这看似简单,实则埋雷:
- 调用方必须手动类型断言
v, ok := r.Value.(string),一漏就 panic - 编译器无法检查你是否处理了
Err,和裸写val, err := fn()相比,反而更易忽略错误 - 无法约束
Value和Err的互斥性:理论上Value非空时Err应为 nil,但结构体不强制
用泛型定义 Result[T] 的最小可行写法
核心是让类型参数 T 锁定成功值类型,同时禁止 Value 和 Err 同时有效:
- 定义为
type Result[T any] struct { value T; err error; ok bool },ok标记是否成功,比靠err == nil更可靠(尤其当T是指针或接口时) - 只暴露构造函数:
Ok[T](v T) Result[T]和Err[T](e error) Result[T],禁止直接字面量初始化 - 提供
IsOk() bool、Unwrap() (T, error)、Must() T(带 panic)等方法,语义清晰
示例:
func ParseInt(s string) Result[int] {
v, err := strconv.Atoi(s)
if err != nil {
return Err[int](err)
}
return Ok[int](v)
}
调用时怎么安全取值?别直接解包
常见错误是写 v, _ := r.Unwrap() 忽略 error,或者在没检查 IsOk() 就调 Must():
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 推荐模式:先
if r.IsOk() { v := r.Must(); ... },明确分支意图 - 如果要链式处理(比如
ParseInt → Square → ToString),得实现Map(func(T) U) Result[U]和FlatMap(func(T) Result[U]) Result[U],否则嵌套if很难看 - 注意:Go 的
defer和recover无法捕获Must()的 panic,线上慎用
泛型 Result 在 HTTP handler 里怎么用才不破坏生态
Go 的标准库和主流框架(如 net/http、chi)都基于 error 返回,强行塞 Result 会增加胶水代码:
- 不要让 handler 返回
Result[Response];而是把Result用在业务逻辑层,handler 层再转成http.Error或 JSON 响应 - 例如:
if r.IsOk() { json.NewEncoder(w).Encode(r.Must()) } else { http.Error(w, r.Unwrap().Error(), http.StatusBadRequest) } - 切忌在中间件里自动解包
Result——它不是上下文,也不是请求生命周期的一部分
泛型 Result 的价值不在“看起来更函数式”,而在于把错误路径显式编码进类型。但 Go 的惯习是“显式错误检查”,所以真正难的不是定义,是团队能否坚持在每个 Result 上调用 IsOk() 或 Unwrap(),而不是把它当普通 struct 读字段。

















