文章目錄
- 1.變量
- 2.類型
- 3.函數
- 3.1 New
- 3.2 Is
- 簡介
- 函數簽名
- 核心功能
- 示例代碼
- 使用場景
- 注意事項
- 小結
- 3.3 As
- 簡介
- 函數簽名
- 核心功能
- 示例代碼
- 使用場景
- 注意事項
- 小結
- 3.4 Unwrap
- 簡介
- 函數簽名
- 核心功能
- 使用示例
- 使用場景
- 注意事項
- 小結
- 3.5 Join
- 簡介
- 函數簽名
- 核心功能
- 使用場景
- 注意事項
- 小結
- 4.小結
- 參考文獻
在 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。
核心功能
- 遞歸解包錯誤鏈errors.Is 會通過 Unwrap() 或Unwrap() []error方法逐層解包錯誤鏈,檢查每一層錯誤是否與 target 匹配。
- 值相等性檢查檢查錯誤的“值”是否與 target 相等。默認使用 == 操作符比較,但若錯誤類型實現了 Is(error) bool 方法,則優先調用該方法進行判斷(允許自定義相等邏輯)。
- 支持自定義錯誤匹配邏輯如果自定義錯誤類型需要定義特殊的相等規則(例如比較結構體字段而非指針地址),可以實現 Is(error) bool 方法。
示例代碼
package mainimport ("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。
使用場景
- 檢查預定義錯誤例如判斷錯誤是否為 io.EOF 或 os.ErrNotExist:
if errors.Is(err, io.EOF) {// 處理文件結束邏輯
}
- 自定義錯誤匹配當需要根據錯誤的某些字段(而非指針地址)匹配時,自定義 Is 方法:
type ValidationError struct { Field string }
func (e *ValidationError) Is(target error) bool {t, ok := target.(*ValidationError)return ok && e.Field == t.Field
}
- 處理多層錯誤鏈自動遍歷包裹錯誤(如 fmt.Errorf + %w 生成的錯誤鏈):
err := fmt.Errorf("layer2: %w", fmt.Errorf("layer1: %w", originalErr))
if errors.Is(err, originalErr) { // 直接檢查最底層錯誤// 匹配成功
}
與 == 操作符的區別:
- 默認行為:如果錯誤類型未實現 Is 方法,errors.Is 默認使用 == 比較錯誤值和 target。但對于指針類型的錯誤(如 &MyError{}),== 比較的是指針地址而非值內容。
- 自定義邏輯:通過實現 Is 方法,可以控制錯誤的匹配邏輯(如比較結構體字段)。
注意事項
-
優先實現 Is 方法:如果自定義錯誤需要支持值匹配(而非指針匹配),必須實現 Is 方法。
-
target可以是nil:如果 target 為 nil,errors.Is 僅在 err 也為 nil 時返回 true。
-
性能錯誤鏈較長時,遞歸解包可能導致輕微性能開銷,但通常可忽略。
小結
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 mainimport ("errors""fmt"
)// 自定義錯誤類型
type MyError struct {Code intMessage 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 *MyErrorif errors.As(wrappedErr, &myErr) {fmt.Println("Found MyError:", myErr.Code, myErr.Message)// 輸出: Found MyError: 404 Not Found}
}
在這個例子中:
- 自定義錯誤類型 MyError 實現了 error 接口。
- 通過 fmt.Errorf 和 %w 包裹原始錯誤,形成錯誤樹。
- errors.As 檢查包裹后的錯誤樹,找到 MyError 類型的錯誤實例,并將其賦值給 myErr。
使用場景
-
提取特定錯誤類型的詳細信息當錯誤類型包含額外字段(如錯誤碼、上下文信息)時,可以通過 errors.As 提取這些信息。
-
處理標準庫中的錯誤類型例如,檢查一個錯誤是否是 os.PathError 類型,以獲取文件路徑相關的詳細信息:
var pathErr *os.PathError
if errors.As(err, &pathErr) {fmt.Println("Failed at path:", pathErr.Path)
}
- 多層級錯誤解包無需手動調用 Unwrap() 遍歷錯誤鏈,errors.As 會自動處理嵌套錯誤。
注意事項
- target 必須是指針target 必須是一個指向接口或具體類型的指針。例如:
var target *MyError // 正確(具體類型指針)
var target error = &MyError{} // 正確(接口類型指針)
-
類型必須匹配target 的類型需要與錯誤鏈中某個錯誤的具體類型完全一致(或接口類型)。
-
性能如果錯誤樹非常長,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
- 解包錯誤:如果 err 實現了
Unwrap() error
方法,則返回該方法的結果。 - 無包裝時:如果錯誤不支持解包或已經是底層錯誤,返回 nil。
- 簡單直接:僅解包一層,不會遞歸解包整個錯誤鏈。
核心功能
- 解包錯誤鏈用于從包裹錯誤(如通過
fmt.Errorf
和%w
生成的錯誤)中提取下一層錯誤。 - 支持自定義錯誤類型。若自定義錯誤類型實現了
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 {// 處理原始錯誤
}
- 標準接口:要求被解包的錯誤實現 Unwrap() error 方法
- 非遞歸:只解包一層,要解包整個錯誤鏈需要循環調用
- 與 errors.Is/As 配合:errors.Is 和 errors.As 內部會自動處理錯誤鏈
使用場景
- 逐層檢查錯誤鏈通過循環調用 errors.Unwrap 遍歷所有嵌套錯誤:
currentErr := err
for currentErr != nil {fmt.Println(currentErr)currentErr = errors.Unwrap(currentErr)
}
-
結合 errors.Is** 和 **errors.As雖然 errors.Is 和 errors.As 會自動遍歷錯誤鏈,但在需要手動提取特定層級錯誤時,可用 Unwrap 配合使用。
-
自定義錯誤類型的分層處理為自定義錯誤實現 Unwrap() 方法,使其能融入錯誤鏈機制。
注意事項
-
僅解包一層每次調用 errors.Unwrap 只返回直接包裹的下層錯誤。需循環調用以遍歷整個鏈。
-
依賴 Unwrap() 方法只有實現了 Unwrap() error 方法的錯誤才能被正確解包。例如:
- fmt.Errorf 使用 %w 包裹的錯誤會自動實現此方法。
- 自定義錯誤需顯式實現 Unwrap() error。
- 空值處理如果 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。
核心功能
- 合并多個錯誤errors.Join 會將所有非 nil 的錯誤合并為一個包裝錯誤。合并后的錯誤可以通過 errors.Unwrap 獲取所有原始錯誤的切片。
- 兼容 errors.Is和 errors.As合并后的錯誤支持通過 errors.Is 和 errors.As 檢查或提取其中的特定錯誤。例如:
- errors.Is(err, target):如果合并后的錯誤鏈中存在與 target 匹配的錯誤,返回 true。
- errors.As(err, &target):可以提取合并錯誤鏈中的第一個匹配類型的錯誤。
- 錯誤信息拼接合并后的錯誤信息是多個原始錯誤信息的拼接,以換行符分隔。例如:
err1 := errors.New("error 1")
err2 := errors.New("error 2")
joinedErr := errors.Join(err1, err2)
fmt.Println(joinedErr)
// 輸出:
// error 1
// error 2
示例代碼
package mainimport ("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.PathErrorif 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
使用場景
- 并發操作中的錯誤收集在多個協程并發執行時,可以使用 errors.Join 收集所有協程返回的錯誤:
func processTasks(tasks []Task) error {var wg sync.WaitGroupvar mu sync.Mutexvar errs []errorfor _, 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...)
}
- 批量操作中的錯誤匯總例如,處理多個文件或請求時,統一返回所有錯誤:
func batchProcess(files []string) error {var errs []errorfor _, file := range files {if err := processFile(file); err != nil {errs = append(errs, fmt.Errorf("process %s: %w", file, err))}}return errors.Join(errs...)
}
- 兼容已有錯誤處理邏輯合并后的錯誤仍然可以被 errors.Is 和 errors.As 處理,無需修改現有代碼。
注意事項
-
Go 版本要求errors.Join 僅在 Go 1.20 及以上版本可用。
-
空參數處理如果所有輸入錯誤均為 nil,errors.Join 返回 nil。
-
錯誤解包使用 errors.Unwrap 解包合并后的錯誤時,會返回一個 []error 切片(包含所有非 nil 錯誤)。
-
錯誤順序合并后的錯誤順序與輸入參數的順序一致,但 errors.Is 和 errors.As 會按順序檢查所有錯誤。
小結
errors.Join 提供了一種簡潔的方式將多個錯誤合并為一個,特別適用于需要匯總多個錯誤信息的場景(如并發編程或批量處理)。通過結合 errors.Is 和 errors.As,可以靈活地檢查或提取合并后的錯誤鏈中的特定錯誤。它是 Go 錯誤處理工具箱中的重要補充,進一步提升了錯誤管理的便利性。
4.小結
errors 包是 Go 語言標準庫中用于錯誤處理的核心包,隨著 Go 版本的演進,它提供了越來越強大的錯誤處理能力。
以下是主要功能的總結:
- 基礎錯誤創建
- New(text string) error:創建簡單的錯誤對象
- 示例:err := errors.New(“file not found”)
- 錯誤檢查
- Is(err, target error) bool:檢查錯誤鏈中是否包含特定錯誤
- 示例:if errors.Is(err, os.ErrNotExist) {…}
- 錯誤類型提取
- As(err error, target interface{}) bool:從錯誤鏈中提取特定類型的錯誤
- 示例:var perr *fs.PathError; if errors.As(err, &perr) {…}
- 錯誤包裝與解包
- Unwrap(err error) error:解包一層錯誤
- 通過 fmt.Errorf 的 %w 動詞包裝錯誤
- 示例:wrapped := fmt.Errorf(“context: %w”, err)
- 錯誤組合
- Join(errs …error) error(Go 1.20+):合并多個錯誤為一個組合錯誤
- 示例:combined := errors.Join(err1, err2, err3)
errors 包與標準庫中的其他錯誤類型(如 os.PathError、net.OpError 等)配合使用,構成了 Go 強大的錯誤處理體系。
參考文獻
pkg.go.dev/errors