errors.Is() 和 errors.As()
本课属于 Coddy GO 学习旅程的“面向对象编程”单元。第 56 课,共 107 课。
错误被包装后,像 err == ErrNotFound 这样的简单相等性检查就不再有效。errors 包提供了两个函数,用于检查包装后的错误链:errors.Is() 和 errors.As()。
errors.Is() 会检查错误链中的任何错误是否与特定的哨兵错误匹配:
import "errors"
var ErrNotFound = errors.New("not found")
func FindUser(id int) error {
return fmt.Errorf("database lookup failed: %w", ErrNotFound)
}
err := FindUser(42)
if errors.Is(err, ErrNotFound) {
fmt.Println("User doesn't exist")
}即使返回的错误被包装起来,errors.Is() 也会解包错误链,并找到其中的 ErrNotFound。
errors.As() 会检查错误链中的任何错误是否与特定的类型匹配,并提取该错误:
type ValidationError struct {
Field string
}
func (e *ValidationError) Error() string {
return "validation failed: " + e.Field
}
err := fmt.Errorf("request failed: %w", &ValidationError{Field: "email"})
var ve *ValidationError
if errors.As(err, &ve) {
fmt.Println("Invalid field:", ve.Field)
}检查哨兵错误时使用 errors.Is(),需要访问自定义错误类型的字段时使用 errors.As()。这两个函数都会遍历整个错误链,因此对于正确处理包装后的错误至关重要。
挑战
简单让我们构建一个文件处理系统,演示如何使用 errors.Is() 和 errors.As() 正确检查包装后的错误链。你将创建错误,让它们在经过不同层时被包装,然后使用这些函数识别错误并提取其中的信息。
你将把代码组织在两个文件中:
errors.go:为文件系统定义错误基础结构。创建一个哨兵错误:
ErrPermissionDenied,错误消息为"permission denied"
创建一个自定义错误类型:
FileError结构体,包含Filename(字符串)和Operation(字符串)字段- 它的
Error()方法应返回:[Operation] failed on file: [Filename]
实现两个模拟文件操作并包装错误的函数:
ReadFile(filename string) error:如果 filename 是"secret.txt",则返回ErrPermissionDenied。如果 filename 是"missing.txt",则返回一个*FileError,其中 Operation 为"read",Filename 为给定的文件名。否则返回nil。ProcessFile(filename string) error:调用ReadFile。如果返回错误,则使用fmt.Errorf和格式"processing failed: %w"包装该错误。否则返回nil。
main.go:从输入中读取文件名并调用ProcessFile。使用errors.Is()和errors.As()检查包装后的错误链,并根据找到的内容打印相应消息。
输入内容如下:
- 第 1 行:要处理的文件名
通过检查错误链来处理结果:
- 如果
errors.Is()在错误链中找到ErrPermissionDenied:打印完整的错误消息,然后换行打印Access denied - check file permissions - 如果
errors.As()在错误链中找到*FileError:打印完整的错误消息,然后换行打印File issue: [Filename] during [Operation] - 如果没有发生错误:打印
File '[filename]' processed successfully
例如,输入 secret.txt 时,输出应为:
processing failed: permission denied
Access denied - check file permissions输入 missing.txt 时,输出应为:
processing failed: read failed on file: missing.txt
File issue: missing.txt during read输入 data.txt 时,输出应为:
File 'data.txt' processed successfully请注意,尽管错误被 ProcessFile 包装,你仍然可以使用 errors.Is() 检测原始哨兵错误,并使用 errors.As() 提取自定义错误类型。这些函数会遍历整个错误链,查找你要找的内容。
亲手试试
package main
import (
"errors"
"fmt"
)
func main() {
// 从输入中读取文件名
var filename string
fmt.Scanln(&filename)
// 用文件名调用 ProcessFile
err := ProcessFile(filename)
// TODO:检查错误链并处理结果
//
// 用 errors.Is() 检查错误链中是否包含 ErrPermissionDenied:
// 如果找到了:先打印完整的错误,再打印 "Access denied - check file permissions"
//
// 用 errors.As() 检查错误链中是否包含 *FileError:
// 如果找到了:先打印完整的错误,再打印 "File issue: [Filename] during [Operation]"
//
// 如果没有错误:打印 "File '[filename]' processed successfully"
_ = errors.Is // 提示:使用 errors.Is()
_ = errors.As // 提示:使用 errors.As()
_ = err
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Go 在线编译器