Go语言的标准库errors包是处理错误的基础工具,从Go 1.13版本开始,该包新增了错误包装、错误链判断等能力,大幅提升了错误处理的灵活性。合理利用errors包的功能,可以让错误信息的传递更清晰,错误排查更便捷,避免传统错误处理中信息丢失、判断繁琐的问题。
errors包基础功能:创建和判断错误
创建简单错误
errors包最常用的能力是快速创建错误实例,通过errors.New函数可以生成一个包含指定错误信息的错误对象,该函数接收一个字符串参数作为错误描述。
package main
import (
"errors"
"fmt"
)
func divide(a, b int) (int, error) {
if b == 0 {
// 创建除零错误
return 0, errors.New("除数不能为0")
}
return a / b, nil
}
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Println("计算失败:", err)
return
}
fmt.Println("计算结果:", result)
}
判断错误类型
在Go 1.13之前,判断错误通常需要比较错误实例是否相等,或者使用类型断言判断错误类型。errors包配合==运算符可以直接判断错误是否为预期的错误。
package main
import (
"errors"
"fmt"
)
var ErrInvalidParam = errors.New("参数不合法")
func checkParam(param int) error {
if param < 0 {
return ErrInvalidParam
}
return nil
}
func main() {
err := checkParam(-1)
if err == ErrInvalidParam {
fmt.Println("捕获到参数错误:", err)
}
}
Go 1.13+ errors包进阶功能:错误包装与链判断
错误包装
错误包装允许在传递错误时附加更多上下文信息,同时保留原始错误的引用,形成错误链。使用fmt.Errorf配合%w动词可以包装错误,包装后的错误可以通过errors.Unwrap获取原始错误。
package main
import (
"errors"
"fmt"
)
func readFile(path string) error {
// 模拟原始错误
originalErr := errors.New("文件不存在")
// 包装错误,附加文件路径上下文
return fmt.Errorf("读取文件 %s 失败: %w", path, originalErr)
}
func main() {
err := readFile("/tmp/test.txt")
fmt.Println("错误信息:", err)
// 获取原始错误
unwrapErr := errors.Unwrap(err)
fmt.Println("原始错误:", unwrapErr)
}
错误链判断
对于包装后的错误,不能使用==直接判断原始错误类型,需要使用errors.Is函数,该函数会遍历错误链,判断链中是否存在目标错误。
package main
import (
"errors"
"fmt"
)
var ErrFileNotFound = errors.New("文件不存在")
func readFile(path string) error {
// 包装原始错误
return fmt.Errorf("读取文件 %s 失败: %w", path, ErrFileNotFound)
}
func main() {
err := readFile("/tmp/test.txt")
// 判断错误链中是否包含ErrFileNotFound
if errors.Is(err, ErrFileNotFound) {
fmt.Println("捕获到文件不存在错误")
}
}
提取特定错误类型
如果错误是自定义错误类型,需要获取错误的具体信息,可以使用errors.As函数,该函数会遍历错误链,找到匹配的目标错误类型并赋值给目标变量。
package main
import (
"errors"
"fmt"
)
// 自定义错误类型
type MyError struct {
Code int
Message string
}
func (e *MyError) Error() string {
return fmt.Sprintf("错误码: %d, 错误信息: %s", e.Code, e.Message)
}
func doTask() error {
// 返回自定义错误并包装
return fmt.Errorf("任务执行失败: %w", &MyError{Code: 500, Message: "内部服务异常"})
}
func main() {
err := doTask()
var myErr *MyError
// 提取自定义错误类型
if errors.As(err, &myErr) {
fmt.Printf("捕获到自定义错误,错误码: %d, 信息: %sn", myErr.Code, myErr.Message)
}
}
errors包使用最佳实践
- 对于可复用的 sentinel 错误(哨兵错误),建议使用
errors.New定义为包级变量,方便外部判断。 - 传递错误时尽量使用错误包装,附加当前场景的上下文信息,避免直接返回原始错误导致信息缺失。
- 判断错误时优先使用
errors.Is和errors.As,而不是==或类型断言,兼容错误包装的场景。 - 不要在错误中附加敏感信息,错误最终可能会输出到日志或返回给调用方,避免信息泄露。
常见问题解答
errors.New和fmt.Errorf有什么区别?
errors.New只能创建简单的错误实例,而fmt.Errorf可以通过格式化字符串生成更丰富的错误信息,配合%w还能实现错误包装,功能更强大。
错误包装会增加性能开销吗?
错误包装的性能开销非常小,仅在错误传递时附加少量上下文和原始错误引用,对整体程序性能几乎无影响,相比带来的错误排查便利性,这点开销完全可以接受。
如何自定义错误类型同时满足errors.Is判断?
自定义错误类型只需要实现Error() string方法即可,如果需要支持errors.Is判断,可以在自定义错误类型中实现Unwrap() error方法,返回被包装的原始错误。
Goerrors包错误处理error_wrapping修改时间:2026-06-23 16:42:30