开发者工具 · Go 全栈

Go errors 速查

errors.Is/As/Wrap/pkg/errors 速查

✓本地处理 · 不上传 ✓免费 · 无需登录 ✓无次数限制 累计 113 次使用

Go 错误处理速查

errors.Is / As / Wrap / pkg/errors · 点击复制

第一节

关于本工具

About

调试 Go 代码时,errors.Is 和 errors.As 的签名总是记混,尤其是 pkg/errors 的 Wrap 与标准库的 %w 行为差异。这个工具列出了 errors 包和 pkg/errors 最常用的 12 个函数签名、返回类型与典型用法,每个条目附带一段可运行的示例代码片段。纯浏览器渲染,代码不离开本地,适合写单元测试前快速确认 API 行为。

使用场景

壹

重构时排查错误链

后端工程师老赵在重构用户模块时,发现 log 里同时出现 'not found' 和 'permission denied' 两种 error。他需要确认业务层抛出的 'not found' 是否源于底层数据库的 'sql.ErrNoRows',而非其他包误包装的同类字符串。用 errors.Is 逐层拆解,一眼看出错误链的根因,不用再逐行打 log 或手动追源码。

贰

跨服务调用错误归因

微服务 A 调用 B 的 gRPC 接口返回 'context deadline exceeded',但 B 侧日志显示处理耗时仅 50ms。怀疑是 A 的客户端超时设置过短,但 B 内部又用 fmt.Errorf 包装了多层信息。把错误对象丢进本工具,errors.As 能提取出底层的 'DeadlineExceeded' 类型,直接定位到是 gRPC 拦截器层抛出的,而非业务逻辑。

叁

第三方库错误类型转换

用 'github.com/go-sql-driver/mysql' 时,驱动返回的 'mysql.MySQLError' 包含 Number 字段(如 1062 表示唯一键冲突)。业务代码需要根据 Number 决定重试还是报错。用 errors.As 把 error 断言成 *mysql.MySQLError,再读 Number 字段。本工具能快速验证该断言是否匹配,避免运行时 panic。

肆

pkg/errors 栈轨迹调试

线上偶发 'nil pointer' panic,但标准 error 只显示 'runtime error: invalid memory address'。如果代码里用了 'github.com/pkg/errors' 的 errors.Wrap,本工具能解析出被包装的原始 error 及其调用栈(file:line 信息),帮测试同学小陈从 2000 行代码的 release 分支里,锁定具体是哪次 commit 引入的 nil 判断遗漏。

伍

自定义错误类型兼容性检查

团队自定义了 'ErrValidationFailed' 类型,实现了 Error() 方法,但忘了实现 Is() 方法导致 errors.Is 无法匹配。把自定义类型和标准 error 输入本工具,模拟 errors.Is 的匹配逻辑,能直接发现 Is() 缺失,而不需要写单元测试跑一遍。

第二节

使用指南

Getting Started

使用步骤

  1. 1在「错误类型」下拉框选择目标错误(如 `os.ErrNotExist`),右侧立即列出该错误的所有匹配函数与代码示例
  2. 2在「函数筛选」输入框键入 `Is` 或 `As`,结果列表自动过滤,只显示包含该关键字的用法条目
  3. 3点击任意用法条目(如 `errors.Is(err, target)`),下方「代码示例」区展开对应 Go 代码片段,可直接复制
  4. 4在「包来源」标签页切换(标准库 / `pkg/errors`),列表内容同步更新,标注各函数的包路径与版本要求

输入输出示例

输入输出说明
errors.Is(err, io.EOF)true常规:判断错误链中是否包含特定哨兵错误,errors.Is 会沿 Wrap 链向上查找
errors.As(err, &target)true, target = *os.PathError常规:将错误链中匹配类型的错误赋值给 target,用于获取具体错误信息
fmt.Errorf("wrap: %w", io.EOF)wrap: EOF常规:使用 %w 格式化动词创建带 Wrap 的错误,errors.Is 可匹配到 io.EOF
errors.Is(nil, io.EOF)false边界:nil 错误不匹配任何错误,避免误判
errors.As(nil, &target)false边界:nil 错误无法赋值,target 保持不变
errors.Unwrap(fmt.Errorf("wrap: %w", io.EOF))EOF边界:Unwrap 只剥一层,返回被 Wrap 的原始错误
errors.Is(err, errors.New("EOF"))false易错:errors.New 每次创建新实例,即使内容相同也不相等;应使用 io.EOF 等哨兵
errors.As(err, &target) 但 target 类型为 *os.PathError 而 err 是 *net.OpErrorfalse易错:类型不匹配时 As 返回 false,不会 panic,但需检查返回值

常见错误对照

1.errors.Is 直接比较包装后的哨兵错误

✗ 错误errors.Is(err, io.EOF)
✓ 修复errors.Is(err, io.EOF) // 正确,因为 Is 会解包装

errors.Is 会自动遍历错误链,不需要手动解包装。直接写即可,它内部调用 Unwrap 逐层比较。

2.用 == 比较包装后的错误

✗ 错误if err == io.EOF { ... }
✓ 修复if errors.Is(err, io.EOF) { ... }

== 只比较指针地址,包装后的错误是新对象。必须用 errors.Is 遍历链式比较。

3.errors.As 第二个参数传 nil 或非指针

✗ 错误var target *os.PathError; errors.As(err, target)
✓ 修复var target *os.PathError; errors.As(err, &target)

errors.As 第二个参数必须是**指向目标类型指针的指针**,否则无法赋值。传 nil 或直接传 target 会 panic。

4.用 errors.As 匹配接口类型时传接口值

✗ 错误var target error; errors.As(err, &target)
✓ 修复var target *MyError; errors.As(err, &target)

errors.As 要求目标类型是具体类型指针,不能传 error 接口本身。接口无法被赋值,As 会返回 false。

5.fmt.Errorf 用 %w 包装多个错误

✗ 错误fmt.Errorf("failed: %w, %w", err1, err2)
✓ 修复fmt.Errorf("failed: %w", err1) // 或使用 errors.Join

fmt.Errorf 只支持一个 %w 占位符。多个 %w 会导致编译错误。Go 1.20 起可用 errors.Join。

6.pkg/errors.Wrap 返回 nil 时继续使用

✗ 错误if err != nil { return errors.Wrap(err, "msg") } else { return errors.Wrap(nil, "msg") }
✓ 修复if err != nil { return errors.Wrap(err, "msg") } else { return nil }

pkg/errors.Wrap 传入 nil 会返回 nil,但 Wrap 本身有开销。更清晰的做法是只在 err != nil 时包装。

7.自定义错误类型未实现 Unwrap 方法

✗ 错误type MyErr struct { Msg string; Err error }
✓ 修复func (e *MyErr) Unwrap() error { return e.Err }

要让 errors.Is/As 遍历自定义错误链,类型必须实现 Unwrap() error 方法。否则包装链断裂。

8.errors.Is 与 errors.As 混用场景

✗ 错误errors.Is(err, &MyError{})
✓ 修复var target *MyError; errors.As(err, &target)

errors.Is 比较值相等,适合哨兵错误;errors.As 匹配类型,适合提取具体错误实例。传值给 Is 不会匹配类型。

第三节

工作原理

How It Works

核心公式

errors.Is(err, target) ↔ err == target ∥ errors.Is(err.Unwrap(), target)

变量说明

  • err待检查的错误值
  • target目标错误哨兵值
  • err.Unwrap()err 的底层包装错误

示例

err = fmt.Errorf("连接超时: %w", io.EOF),target = io.EOF。errors.Is(err, io.EOF) 先比较 err == io.EOF(false),再递归比较 err.Unwrap() == io.EOF(true),最终返回 true。

输入错误类型(err, target)errors.Is 查表遍历 Unwrap 链逐层 == 比较匹配结果true / falseerrors.As 查表类型断言 + 赋值类型匹配结果true + 赋值 / false
用户输入 本地处理 输出结果
第五节

常见问题

Q & A
问errors.Is 和 errors.As 到底啥区别,我每次都用混+
答

核心区别是匹配目标类型不同。errors.Is 用来判断错误链里有没有“某个特定的值”(比如某个哨兵错误),它做的是值比较,通常用于检查 io.EOF 这种固定错误。errors.As 用来判断错误链里有没有“某种类型的错误”,它会把匹配到的错误赋值给目标变量,适合提取自定义错误里的字段。本速查表里两个函数的关键字高亮和参数类型标注就是帮你一眼区分:Is 第二个参数是 error 值,As 第二个参数是目标指针。

问为什么我用了 errors.Wrap 之后,errors.Is 反而匹配不上了?+
答

这大概率是用了 golang.org/x/xerrors 的 Wrap 而不是 github.com/pkg/errors 或 Go 1.20 的 fmt.Errorf("%w")。xerrors.Wrap 创建的包装错误在 Go 1.13 标准库的 errors.Is 下不兼容,因为它的 Unwrap 方法签名不同。本速查表在“Wrap 实现对比”栏明确列出了标准库 fmt.Errorf、pkg/errors 和 xerrors 三种实现的行文差异。建议迁移到 Go 1.20+ 并用 fmt.Errorf("%w: %v", err, msg) 包装,保证 Is/As 链式匹配正确。

问pkg/errors 已经 deprecated 了,这个速查表还提它干嘛?+
答

pkg/errors 确实在 2021 年归档了,但大量遗留项目(尤其是 Go 1.12 之前启动的老项目)仍然重度依赖它。本速查表保留 pkg/errors 一栏,是因为很多开发者接手旧代码时,面对 errors.Wrap 和 errors.Cause 不知道怎么跟标准库的 errors.Is 互转。表中给出了两种代码的“等价写法对照”,比如 pkg/errors.Cause(err) 在标准库下可以用一个循环 errors.Unwrap 替代。不列它,接手旧项目的人反而更懵。

问我自定义了一个错误类型,怎么让 errors.As 能匹配到它?+
答

关键是要让你的自定义类型实现 error 接口(即有一个 Error() string 方法),并且最好也实现 Unwrap() error 方法,这样它才能正确参与到错误链中。如果想让 errors.As 匹配到指针类型,注意传进去的目标必须是该类型的双指针(**MyError)。本速查表中“自定义错误实现示例”给出了带字段的 MyError 完整代码,包括 Error()、Unwrap() 和 As 匹配的写法,直接复制改字段名就能用。

问用 fmt.Errorf("%w") 包装之后,怎么拿到最里层的原始错误?+
答

标准库没有提供类似 pkg/errors.Cause 的直接函数,但可以自己写一个循环:for { if err = errors.Unwrap(err); err == nil { break } }。注意这样会一路解到最底层,如果中间某个错误没有实现 Unwrap 就停在那层。更安全的做法是用 errors.Is 检查特定哨兵错误,或者用 errors.As 提取中间层的自定义错误。本速查表在“解包技巧”部分列出了三种遍历策略的代码片段,分别对应“取根错误”“取第一个可匹配类型”“取所有层级”。

问这个速查表是纯前端页面吗?我的代码片段会不会被上传到服务器?+
答

本工具完全在浏览器本地运行,没有任何后端服务器。所有 errors 函数签名对比、代码示例高亮、格式化逻辑都通过前端 JavaScript 和 WASM 模块在用户浏览器中执行。输入框里贴的代码或搜索的关键词不会离开当前页面,刷新即消失。可以放心在离线环境或内网开发机上使用。

问有没有 Go 官方文档里没写清楚的 errors 使用陷阱?+
答

有几个常见坑。一是 errors.Is(nil, nil) 返回 false,因为 nil 不是 error。二是用 errors.As 时目标变量必须是非 nil 指针,否则 panic。三是 fmt.Errorf 如果同时用 %w 和 %v 包装多个错误,只有最后一个 %w 生效,前面的 %w 会被当成普通格式化输出。本速查表在“常见陷阱”区域集中列出了 5 条这类边界行为,每条都附了触发代码和输出结果,比官方文档的 FAQ 更贴近实际踩坑场景。

隐私保证所有计算与处理均在你的浏览器本地完成,输入数据不会上传服务器,也不会保存或共享。

↑↓选择 ↵打开 ⌘+↵新窗口 esc关闭