错误哨兵依赖errors.New创建的同一地址错误实例进行==比较,fmt.Errorf每次返回新对象导致比较失败;须定义为公有包级变量,调用方直接==判断。

Go 语言中错误哨兵(error sentinel)不是语法特性,而是约定俗成的错误值复用模式——它靠的是 errors.New 创建的**同一地址的错误实例**做精确比较,而非字符串匹配或类型断言。
为什么必须用 errors.New 而不是 fmt.Errorf
fmt.Errorf 每次调用都返回新分配的错误对象,即使内容相同,== 比较也必为 false;而 errors.New 返回的指针在包初始化时就固定了。
- ✅ 正确:定义为包级变量,如
var ErrNotFound = errors.New("not found") - ❌ 错误:每次调用
fmt.Errorf("not found"),哪怕内容一致,也无法用==判断 - ⚠️ 注意:不能把哨兵错误设为私有(小写),否则外部无法导入和比较
如何在函数中返回并判断哨兵错误
返回时直接用预定义变量;调用方用 == 比较,而不是 errors.Is(后者用于底层包装链,哨兵本身不包装)。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 返回示例:
if id - 判断示例:
if err == ErrInvalidID { ... } - 不要写
if errors.Is(err, ErrInvalidID)—— 多余且掩盖意图,哨兵就是设计来==的 - 若函数可能返回包装错误(如
fmt.Errorf("wrap: %w", ErrNotFound)),则需用errors.Is,此时已不属于纯哨兵场景
常见踩坑:哨兵错误被意外覆盖或重定义
哨兵失效往往不是逻辑错,而是包加载或变量作用域问题。
立即学习“go语言免费学习笔记(深入)”;
- ❌ 在
init()函数里重复赋值:ErrNotFound = errors.New("not found")—— 可能被多次执行,破坏单例性 - ❌ 在多个文件中各自定义同名哨兵:
var ErrNotFound = errors.New("not found")—— 它们是不同变量,==永远为false - ✅ 正确做法:所有哨兵统一定义在
errors.go文件中,导出,且只定义一次 - ? 小技巧:加一个未导出字段(如
var ErrNotFound = &errSentinel{"not found"})可防止外部构造同类错误,但多数项目无需这么严格
哨兵模式的关键在于「唯一地址 + 显式比较」,一旦混用 fmt.Errorf、跨文件重复定义或误用 errors.Is,就退化成字符串匹配陷阱。真要处理复杂错误上下文,该上 errors.As 和自定义错误类型,而不是硬撑哨兵。

















