在Golang里,错误是值,通常用error接口表示。Go 1.13之后,标准库提供了错误包装能力,让我们可以在返回错误时附带上下文,同时不丢失原始错误。最核心的做法是使用fmt.Errorf配合%w动词。

什么是错误包装
错误包装指把一个已有错误包裹进一个新的错误中,新错误携带额外描述,但通过特定方法仍能拿到里面的原始错误。这样调用方既能看到发生了什么,也能判断根因是不是某个已知错误。
fmt.Errorf与%w基础用法
普通fmt.Errorf用%s等占位符会把错误转成字符串,原错误结构丢失。换成%w则生成包装错误:
package main
import (
"errors"
"fmt"
)
func readFile() error {
return errors.New("file not found")
}
func process() error {
err := readFile()
if err != nil {
// 使用 %w 包装错误,保留底层错误
return fmt.Errorf("process failed: %w", err)
}
return nil
}
func main() {
err := process()
if err != nil {
fmt.Println(err)
}
}
上面代码中,process函数返回的错包含了readFile的原始错误,但增加了process failed前缀。
如何判断包装错误
包装后的错误不能用等号比较,要用errors.Is和errors.As:
package main
import (
"errors"
"fmt"
)
var ErrNotFound = errors.New("not found")
func foo() error {
return fmt.Errorf("wrap: %w", ErrNotFound)
}
func main() {
err := foo()
// 判断错误链中是否包含 ErrNotFound
if errors.Is(err, ErrNotFound) {
fmt.Println("got not found error")
}
}
%w与%s的区别
| 占位符 | 是否保留错误类型 | 能否用errors.Is |
|---|---|---|
| %s | 否,转成字符串 | 不能 |
| %w | 是,包装为链 | 能 |
多层错误包装示例
实际业务中常有多层调用,每一层都可包装:
package main
import (
"errors"
"fmt"
)
func dao() error {
return errors.New("timeout")
}
func service() error {
return fmt.Errorf("service: %w", dao())
}
func handler() error {
return fmt.Errorf("handler: %w", service())
}
func main() {
err := handler()
fmt.Println(err)
// 可一路拆出最底层的 timeout 错误
if errors.Is(err, errors.New("timeout")) {
// 此处不会命中,因为是新实例,仅演示结构
}
}
使用建议
- 只在需要附加上下文且要保留原错误时用%w
- 不要对同一个错误重复包装多次导致信息冗余
- 在库的最底层定义哨兵错误,上层用errors.Is判断
通过fmt.Errorf的%w,Golang的错误处理变得更灵活,既能看清调用链,也不破坏错误判断逻辑。
Golangfmt.Errorf%w修改时间:2026-07-27 02:06:09