Golang errors 包快速上手

发布于:2025-04-18 ⋅ 阅读:(23) ⋅ 点赞:(0)


在 Golang 中,errors 包是用于处理错误的标准库, errors 包提供的功能比较简单,使用起来非常方便。

接下来具体讲解一下 errors 包提供的变量、类型和函数。

1.变量

errors 包只定义了一个全局变量 ErrUnsupported。

var ErrUnsupported = New("unsupported operation")

ErrUnsupported 表示请求的操作不能执行,因为它不受支持。例如,调用os.Link()当使用的文件系统不支持硬链接时。

函数和方法不应该返回这个错误,而应该返回一个包含适当上下文的错误,满足:

errors.Is(err, errors.ErrUnsupported)

要么直接包装 ErrUnsupported,要么实现一个 Is 方法。

函数和方法应该说明何种情况下会返回包含 ErrUnsupported 的错误。

2.类型

error 是一个内建的接口类型,任何类型只要实现了 Error() string 方法,就实现了 error 接口,这意味着该类型的实例可以被当作一个 error 来处理。

// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
    Error() string
}

3.函数

3.1 New

errors.New 用于创建一个新的错误对象。它接收一个字符串作为错误消息,并返回一个错误对象。

func New(text string) error

我们可以看下其具体实现:

// New returns an error that formats as the given text.
// Each call to New returns a distinct error value even if the text is identical.
func New(text string) error {
    return &errorString{text}
}

// errorString is a trivial implementation of error.
type errorString struct {
    s string
}

func (e *errorString) Error() string {
    return e.s
}

可以看到,New 返回的是实现了 error 接口的具体类型 *errorString。

3.2 Is

简介

errors.Is 函数是一个用于错误处理的核心工具,用于检查错误链(error chain)中是否存在某个特定的错误实例。

它是 Go 1.13 版本引入的错误处理增强功能之一,与 errors.As 和 errors.Unwrap 共同提供了更灵活的错误处理机制。

函数签名

func Is(err, target error) bool
  • err: 要检查的错误。
  • target: 我们想要确认的错误。
  • 返回值: 如果错误链中任一错误与目标错误相同,则返回 true。

核心功能

  1. 递归解包错误链errors.Is 会通过 Unwrap() 或Unwrap() []error方法逐层解包错误链,检查每一层错误是否与 target 匹配。
  2. 值相等性检查检查错误的“值”是否与 target 相等。默认使用 == 操作符比较,但若错误类型实现了 Is(error) bool 方法,则优先调用该方法进行判断(允许自定义相等逻辑)。
  3. 支持自定义错误匹配逻辑如果自定义错误类型需要定义特殊的相等规则(例如比较结构体字段而非指针地址),可以实现 Is(error) bool 方法。

示例代码

package main

import (
    "errors"
    "fmt"
)

var ErrNotFound = errors.New("not found")

func main() {
    err := fmt.Errorf("context: %w", ErrNotFound)
    if errors.Is(err, ErrNotFound) {
        fmt.Println("错误链中包含 ErrNotFound")
    }

    errNotFoundNew := errors.New("not found")
    fmt.Println(errors.Is(errNotFoundNew, ErrNotFound)) // false
}

运行输出:

错误链中包含 ErrNotFound
false

因为 err 是基于 ErrNotFound 包装出来的,所以 Is 判断返回 true。

因为 errNotFoundNew 是一个新的 error,虽然错误内容与 ErrNotFound 相同,但是二者是两个独立的 error 对象,所以 Is 判断返回 false。

使用场景

  1. 检查预定义错误例如判断错误是否为 io.EOF 或 os.ErrNotExist:
if errors.Is(err, io.EOF) {
    // 处理文件结束逻辑
}
  1. 自定义错误匹配当需要根据错误的某些字段(而非指针地址)匹配时,自定义 Is 方法:
type ValidationError struct { Field string }
func (e *ValidationError) Is(target error) bool {
    t, ok := target.(*ValidationError)
    return ok && e.Field == t.Field
}
  1. 处理多层错误链自动遍历包裹错误(如 fmt.Errorf + %w 生成的错误链):
err := fmt.Errorf("layer2: %w", fmt.Errorf("layer1: %w", originalErr))
if errors.Is(err, originalErr) { // 直接检查最底层错误
    // 匹配成功
}

与 == 操作符的区别:

  • 默认行为:如果错误类型未实现 Is 方法,errors.Is 默认使用 == 比较错误值和 target。但对于指针类型的错误(如 &MyError{}),== 比较的是指针地址而非值内容。
  • 自定义逻辑:通过实现 Is 方法,可以控制错误的匹配逻辑(如比较结构体字段)。

注意事项

  1. 优先实现 Is 方法:如果自定义错误需要支持值匹配(而非指针匹配),必须实现 Is 方法。

  2. target可以是nil:如果 target 为 nil,errors.Is 仅在 err 也为 nil 时返回 true。

  3. 性能错误链较长时,递归解包可能导致轻微性能开销,但通常可忽略。

小结

errors.Is 是 Go 错误处理的基石之一,它通过递归解包错误链,提供了一种安全、统一的方式来检查特定错误的存在。结合以下实践可最大化其效用:

  • 对需要值匹配的自定义错误实现 Is 方法。
  • 优先使用 errors.Is 而非 == 直接比较错误(确保兼容错误链)。
  • 与 errors.As 分工:Is 用于值匹配,As 用于类型提取。

3.3 As

简介

Golang 中的 errors.As 函数是一个用于错误处理的重要工具,它提供了一种类型安全的方式,用于检查错误树中是否存在某个特定类型的错误,并提取该类型的错误实例。

它是 Go 1.13 引入的错误处理增强功能之一,与 errors.Is 和 errors.Unwrap 共同构成了更灵活的错误处理机制。

函数签名

func As(err error, target any) bool
  • err: 要检查的错误树。
  • target: 一个指向目标类型的指针,用于存储结果。
  • 返回值: 如果错误树中存在指定的类型,则返回 true,并且 target 参数会被设置为相应的错误值。

核心功能

errors.As 会递归遍历错误树(通过 Unwrap() 或 Unwrap() []error 方法解包错误),检查是否存在与 target 类型匹配的错误。如果找到,它会将匹配的错误值赋值给 target,并返回 true。

如果错误的具体值可分配给 target 所指向的值,或者如果错误有一个方法As(any) bool使得As(target)返回true,则错误匹配 target。在后一种情况下,As 方法负责将错误值设置到 target。

与 errors.Is 的区别:

  • errors.Is(err, target):检查错误树中是否存在值等于 target 的错误(值比较)。
  • errors.As(err, &target):检查错误树中是否存在类型与 target 匹配的错误(类型断言)。

示例代码

package main

import (
    "errors"
    "fmt"
)

// 自定义错误类型
type MyError struct {
    Code    int
    Message string
}

func (e *MyError) Error() string {
    return fmt.Sprintf("code: %d, msg: %s", e.Code, e.Message)
}

func main() {
    err := &MyError{Code: 404, Message: "Not Found"}
    wrappedErr := fmt.Errorf("wrapper: %w", err) // 包裹错误

    var myErr *MyError
    if errors.As(wrappedErr, &myErr) {
        fmt.Println("Found MyError:", myErr.Code, myErr.Message)
        // 输出: Found MyError: 404 Not Found
    }
}

在这个例子中:

  1. 自定义错误类型 MyError 实现了 error 接口。
  2. 通过 fmt.Errorf 和 %w 包裹原始错误,形成错误树。
  3. errors.As 检查包裹后的错误树,找到 MyError 类型的错误实例,并将其赋值给 myErr。

使用场景

  1. 提取特定错误类型的详细信息当错误类型包含额外字段(如错误码、上下文信息)时,可以通过 errors.As 提取这些信息。

  2. 处理标准库中的错误类型例如,检查一个错误是否是 os.PathError 类型,以获取文件路径相关的详细信息:

var pathErr *os.PathError
if errors.As(err, &pathErr) {
    fmt.Println("Failed at path:", pathErr.Path)
}
  1. 多层级错误解包无需手动调用 Unwrap() 遍历错误链,errors.As 会自动处理嵌套错误。

注意事项

  1. target 必须是指针target 必须是一个指向接口或具体类型的指针。例如:
var target *MyError        // 正确(具体类型指针)
var target error = &MyError{} // 正确(接口类型指针)
  1. 类型必须匹配target 的类型需要与错误链中某个错误的具体类型完全一致(或接口类型)。

  2. 性能如果错误树非常长,errors.As 可能需要遍历整个树,但实际场景中性能影响通常可忽略。

小结

errors.As 是 Go 错误处理中类型断言的最佳实践,它简化了从错误链中提取特定类型错误的操作。结合 errors.Is 和错误包裹(fmt.Errorf + %w),可以构建清晰、可维护的错误处理逻辑。

3.4 Unwrap

简介

errors.Unwrap 是 Go 1.13 引入的错误处理函数,用于获取被包装(wrapped)错误的原始错误。它是 Go 错误处理机制中错误链(error chain)支持的核心部分。

函数签名

func Unwrap(err error) error
  1. 解包错误:如果 err 实现了 Unwrap() error 方法,则返回该方法的结果。
  2. 无包装时:如果错误不支持解包或已经是底层错误,返回 nil。
  3. 简单直接:仅解包一层,不会递归解包整个错误链。

核心功能

  1. 解包错误链用于从包裹错误(如通过 fmt.Errorf 和 %w 生成的错误)中提取下一层错误。
  2. 支持自定义错误类型若自定义错误类型实现了 Unwrap() error 方法,errors.Unwrap 可自动调用它来解包错误。

使用示例

err := fmt.Errorf("wrapper: %w", io.EOF)

unwrapped := errors.Unwrap(err)
fmt.Println(unwrapped == io.EOF) // 输出: true
fmt.Println(unwrapped)          // 输出: EOF
errors.Unwrap 通常与 fmt.Errorf 的 %w 动词配合使用:
func process() error {
    if err := step1(); err != nil {
        return fmt.Errorf("step1 failed: %w", err)
    }
    // ...
}

err := process()
if errors.Unwrap(err) != nil {
    // 处理原始错误
}
  1. 标准接口:要求被解包的错误实现 Unwrap() error 方法
  2. 非递归:只解包一层,要解包整个错误链需要循环调用
  3. 与 errors.Is/As 配合:errors.Is 和 errors.As 内部会自动处理错误链

使用场景

  1. 逐层检查错误链通过循环调用 errors.Unwrap 遍历所有嵌套错误:
currentErr := err
for currentErr != nil {
    fmt.Println(currentErr)
    currentErr = errors.Unwrap(currentErr)
}
  1. 结合 errors.Is** 和 **errors.As虽然 errors.Is 和 errors.As 会自动遍历错误链,但在需要手动提取特定层级错误时,可用 Unwrap 配合使用。

  2. 自定义错误类型的分层处理为自定义错误实现 Unwrap() 方法,使其能融入错误链机制。

注意事项

  1. 仅解包一层每次调用 errors.Unwrap 只返回直接包裹的下层错误。需循环调用以遍历整个链。

  2. 依赖 Unwrap() 方法只有实现了 Unwrap() error 方法的错误才能被正确解包。例如:

  • fmt.Errorf 使用 %w 包裹的错误会自动实现此方法。
  • 自定义错误需显式实现 Unwrap() error。
  1. 空值处理如果 err 为 nil,或未实现 Unwrap,或 Unwrap 返回 nil,则函数返回 nil。

小结

errors.Unwrap 是处理错误链的基础工具,适用于需要手动逐层解包错误的场景。结合 errors.Is 和 errors.As 可以实现更高效和安全的错误检查。在实际开发中,优先使用 errors.Is 和 errors.As 来操作错误链,仅在需要直接访问特定层级错误时使用 errors.Unwrap。

3.5 Join

简介

Golang 中的 errors.Join 函数是 Go 1.20 版本引入的一个错误处理工具,用于将多个错误合并为一个包装错误(wrapped error)。

它特别适用于需要同时处理多个错误(例如并发操作中多个协程返回错误)的场景。

函数签名

func Join(errs ...error) error
  • 参数 errs …error:一个可变参数列表,接收多个 error 类型的值。
  • 返回值 如果输入的 errs 中存在至少一个非 nil 的错误,则返回一个合并后的包装错误;否则返回 nil。

核心功能

  1. 合并多个错误errors.Join 会将所有非 nil 的错误合并为一个包装错误。合并后的错误可以通过 errors.Unwrap 获取所有原始错误的切片。
  2. 兼容 errors.Is和 errors.As合并后的错误支持通过 errors.Is 和 errors.As 检查或提取其中的特定错误。例如:
  • errors.Is(err, target):如果合并后的错误链中存在与 target 匹配的错误,返回 true。
  • errors.As(err, &target):可以提取合并错误链中的第一个匹配类型的错误。
  1. 错误信息拼接合并后的错误信息是多个原始错误信息的拼接,以换行符分隔。例如:
err1 := errors.New("error 1")
err2 := errors.New("error 2")
joinedErr := errors.Join(err1, err2)
fmt.Println(joinedErr)
// 输出:
// error 1
// error 2
示例代码
package main

import (
    "errors"
    "fmt"
    "io/fs"
    "os"
)

func main() {
    err1 := errors.New("file not found")

    // 创建一个 fs.PathError 错误
    pathErr := &fs.PathError{
        Op:   "open",
        Path: "/etc/passwd",
        Err:  os.ErrPermission,
    }
    err2 := fmt.Errorf("operation failed: %w", pathErr)

    // 合并多个错误
    joinedErr := errors.Join(err1, err2)

    // 打印合并后的错误信息
    fmt.Println("Joined error:")
    fmt.Println(joinedErr)

    // 检查是否包含特定错误
    if errors.Is(joinedErr, err1) {
        fmt.Println("Found 'file not found' error")
    }

    // 提取错误链中的某个类型
    var targetErr *fs.PathError
    if errors.As(joinedErr, &targetErr) {
        fmt.Println("Found PathError:", targetErr)
    }
}

运行输出:

Joined error:
file not found
operation failed: open /etc/passwd: permission denied
Found 'file not found' error
Found PathError: open /etc/passwd: permission denied

使用场景

  1. 并发操作中的错误收集在多个协程并发执行时,可以使用 errors.Join 收集所有协程返回的错误:
func processTasks(tasks []Task) error {
    var wg sync.WaitGroup
    var mu sync.Mutex
    var errs []error

    for _, task := range tasks {
        wg.Add(1)
        go func(t Task) {
            defer wg.Done()
            if err := t.Run(); err != nil {
                mu.Lock()
                errs = append(errs, err)
                mu.Unlock()
            }
        }(task)
    }

    wg.Wait()
    return errors.Join(errs...)
}
  1. 批量操作中的错误汇总例如,处理多个文件或请求时,统一返回所有错误:
func batchProcess(files []string) error {
    var errs []error
    for _, file := range files {
        if err := processFile(file); err != nil {
            errs = append(errs, fmt.Errorf("process %s: %w", file, err))
        }
    }
    return errors.Join(errs...)
}
  1. 兼容已有错误处理逻辑合并后的错误仍然可以被 errors.Is 和 errors.As 处理,无需修改现有代码。

注意事项

  1. Go 版本要求errors.Join 仅在 Go 1.20 及以上版本可用。

  2. 空参数处理如果所有输入错误均为 nil,errors.Join 返回 nil。

  3. 错误解包使用 errors.Unwrap 解包合并后的错误时,会返回一个 []error 切片(包含所有非 nil 错误)。

  4. 错误顺序合并后的错误顺序与输入参数的顺序一致,但 errors.Is 和 errors.As 会按顺序检查所有错误。

小结

errors.Join 提供了一种简洁的方式将多个错误合并为一个,特别适用于需要汇总多个错误信息的场景(如并发编程或批量处理)。通过结合 errors.Is 和 errors.As,可以灵活地检查或提取合并后的错误链中的特定错误。它是 Go 错误处理工具箱中的重要补充,进一步提升了错误管理的便利性。

4.小结

errors 包是 Go 语言标准库中用于错误处理的核心包,随着 Go 版本的演进,它提供了越来越强大的错误处理能力。

以下是主要功能的总结:

  1. 基础错误创建
  • New(text string) error:创建简单的错误对象
  • 示例:err := errors.New(“file not found”)
  1. 错误检查
  • Is(err, target error) bool:检查错误链中是否包含特定错误
  • 示例:if errors.Is(err, os.ErrNotExist) {…}
  1. 错误类型提取
  • As(err error, target interface{}) bool:从错误链中提取特定类型的错误
  • 示例:var perr *fs.PathError; if errors.As(err, &perr) {…}
  1. 错误包装与解包
  • Unwrap(err error) error:解包一层错误
  • 通过 fmt.Errorf 的 %w 动词包装错误
  • 示例:wrapped := fmt.Errorf(“context: %w”, err)
  1. 错误组合
  • Join(errs …error) error(Go 1.20+):合并多个错误为一个组合错误
  • 示例:combined := errors.Join(err1, err2, err3)

errors 包与标准库中的其他错误类型(如 os.PathError、net.OpError 等)配合使用,构成了 Go 强大的错误处理体系。


参考文献

pkg.go.dev/errors


网站公告

今日签到

点亮在社区的每一天
去签到