Go 1.13中errors包的新變化示例解析
Go 1.13 中 errors 包有了一些變化
這些變化是為了更好地支持 Go 的錯(cuò)誤處理提案。Go 1.20 中也增加了一個(gè)新方法,這個(gè)新方法可以代替第三方的庫(kù)處理多個(gè) error,這篇文章將介紹這些變化。
因?yàn)樵瓉淼?Go 的 errors 中的內(nèi)容非常的簡(jiǎn)單,可能會(huì)導(dǎo)致大家輕視這個(gè)包,對(duì)于新的變化不是那么的關(guān)注。讓我們一一介紹這些新的方法。
Unwrap
如果一個(gè) err 實(shí)現(xiàn)了Unwrap
函數(shù),那么errors.Unwrap
會(huì)返回這個(gè) err 的unwrap
方法的結(jié)果,否則返回 nil。 一般標(biāo)準(zhǔn)的 error 都沒有實(shí)現(xiàn)Unwrap
方法,比如io.EOF
, 但是也有一小部分的 error 實(shí)現(xiàn)了Unwrap
方法,比如os.PathError
,os.LinkError
、os.SyscallError
、net.OpError
、net.DNSConfigError
等等。
比如下面的代碼:
fmt.Println(errors.Unwrap(io.EOF)) // nil _, err := net.Dial("tcp", "invalid.address:80") fmt.Println(errors.Unwrap(err))
第一行因?yàn)?code>io.EOF沒有Unwrap
方法,所以輸出 nil。 net.Dial 失敗返回的 err 是*net.OpError
,它實(shí)現(xiàn)了Unwrap
方法,返回更底層的*net.DNSError
,所以第二行輸出為lookup invalid.address: no such host
。
最常用的,我們使用fmt.Errorf
+ %w
包裝一個(gè) error,比如下面的代碼:
e1 := fmt.Errorf("e1: %w", io.EOF) e2 := fmt.Errorf("e2: %w + %w", e1, io.ErrClosedPipe) e3 := fmt.Errorf("e3: %w", e2) e4 := fmt.Errorf("e4: %w", e3) fmt.Println(errors.Unwrap(e4)) // e3: e2: e1: EOF + io: read/write on closed pipe
這段代碼逐層進(jìn)行了包裝,最后的e4
包含了所有的 error,我們可以通過errors.Unwrap
逐層進(jìn)行解包,直到最底層的 error。 fmt.Errorf 可以 1 一次包裝多個(gè) error,比如上面的e2
,它包含了e1
和io.ErrClosedPipe
兩個(gè) error。
我們常常在多層調(diào)用的時(shí)候,把最底層的 error 逐層包裝傳遞上去,這個(gè)時(shí)候我們可以使用fmt.Errorf
+ %w
包裝 error。 在最高層處理 error 的時(shí)候,再逐層Unwrap
解開 error,逐層處理。
Is
Is
函數(shù)檢查 error 的樹中是否包含指定的目標(biāo) error。
啥是 error 的樹? 一個(gè) error 的數(shù)包括它本身,以及通過Unwrap
方法逐層解開的 error。 error 的Unwrap
方法的返回值,可能是單個(gè) error,也可能是是多個(gè) error,在返回多個(gè) error 的時(shí)候,會(huì)采用深度優(yōu)先的方式進(jìn)行遍歷檢查,尋找目標(biāo) error。
怎么才算找到目標(biāo) error 呢?一種情況就是此 err 就是目標(biāo) error,這沒有什么好說的,第二種就是此 err 實(shí)現(xiàn)了Is(err)
方法,把目標(biāo) err 扔進(jìn)Is
方法返回 true。
所以從功能上看Is
函數(shù)其實(shí)叫做Has
函數(shù)更貼切些。
下面是一個(gè)例子:
e1 := fmt.Errorf("e1: %w", io.EOF) e2 := fmt.Errorf("e2: %w + %w", e1, io.ErrClosedPipe) e3 := fmt.Errorf("e3: %w", e2) e4 := fmt.Errorf("e4: %w", e3) fmt.Println(errors.Is(e4, io.EOF)) // true fmt.Println(errors.Is(e4, io.ErrClosedPipe)) // true fmt.Println(errors.Is(e4, io.ErrUnexpectedEOF)) // false
As
Is
是遍歷 error 的數(shù),檢查是否包含目標(biāo) error。As
是遍歷 error 的數(shù),檢查每一個(gè) error,看看是否可以把從 error 賦值給目標(biāo)變量,如果是,則返回 true,并且目標(biāo)變量已賦值,否則返回 false。
下面這個(gè)例子,我們可以看到As
的用法:
if _, err := os.Open("non-existing"); err != nil { var pathError *fs.PathError if errors.As(err, &pathError) { fmt.Println("failed at path:", pathError.Path) } else { fmt.Println(err) } }
如果 os.Open 返回的 error 的樹中包含*fs.PathError
,那么errors.As
會(huì)把這個(gè) error 賦值給pathError
變量,并且返回 true,否則返回 false。 我們這個(gè)例子正好制造的就是文件不存在的 error,所以它會(huì)輸出:failed at path: non-existing
經(jīng)常常犯的一個(gè)錯(cuò)誤就是我們使用一個(gè)error
變量作為As
的第二個(gè)參數(shù)。下面這個(gè)例子 tmp 就是 error 接口類型,所以 origin 可以直接賦值給 tmp,所以errors.As
返回 true,并且 tmp 的值就是 origin 的值。
var origin = fmt.Errorf("error: %w", io.EOF) var tmp = io.ErrClosedPipe if errors.As(origin, &tmp) { fmt.Println(tmp) // error: EOF }
As
使用起來總是那么別別扭扭,每次總得聲明一個(gè)變量,然后把這個(gè)變量傳遞給As
函數(shù),在 Go 支持泛型之后,As
應(yīng)該可以簡(jiǎn)化成如下的方式:
func As[T error](err error "T error") (T, bool)
但是,Go 不會(huì)修改這個(gè)導(dǎo)致不兼容的 API,所以我們只能繼續(xù)保留As
函數(shù),增加一個(gè)新的函數(shù)是一個(gè)可行的方法,無論它叫做IsA
、AsOf
還是AsTarget
或者其他。
如果你已經(jīng)掌握了 Go 的泛型,你可以自己實(shí)現(xiàn)一個(gè)As
函數(shù),比如下面的代碼:
func AsA[T error](err error "T error") (T, bool) { var isErr T if errors.As(err, &isErr) { return isErr, true } var zero T return zero, false }
寫段測(cè)試代碼,我們可以看到它的效果:
type MyError struct{} func (*MyError) Error() string { return "MyError" } func main() { var err error = fmt.Errorf("error: %w", &MyError{}) m, ok := AsA[*MyError](err "*MyError") // MyError does not implement error (Error method has pointer receiver) fmt.Println(m, ok) }
大家在#51945[1]討論了一段時(shí)間,又是無疾而終了。
Join
在我們的項(xiàng)目中,有時(shí)候需要處理多個(gè) error,比如下面的代碼:
func (s *Server) Serve() error { var errs []error if err := s.init(); err != nil { errs = append(errs, err) } if err := s.start(); err != nil { errs = append(errs, err) } if err := s.stop(); err != nil { errs = append(errs, err) } if len(errs) > 0 { return fmt.Errorf("server error: %v", errs) } return nil }
這段代碼中,我們需要處理三個(gè) error,如果有一個(gè) error 不為 nil,那么我們就返回 errs。 當(dāng)然,為了處理多個(gè) errors 情況,先前,有很多的第三方庫(kù)可以供我們使用,比如
go.uber.org/multierr
github.com/hashicorp/go-multierror
github.com/cockroachdb/errors
但是現(xiàn)在,你不用再造輪子或者使用第三方庫(kù)了,因?yàn)?Go 1.20 中增加了errors.Join
函數(shù),它可以把多個(gè) error 合并成一個(gè) error,比如下面的代碼:
var e1 = io.EOF var e2 = io.ErrClosedPipe var e3 = io.ErrNoProgress var e4 = io.ErrShortBuffer _, e5 := net.Dial("tcp", "invalid.address:80") e6 := os.Remove("/path/to/nonexistent/file") var e = errors.Join(e1, e2) e = errors.Join(e, e3) e = errors.Join(e, e4) e = errors.Join(e, e5) e = errors.Join(e, e6) fmt.Println(e.Error()) // 輸出如下,每一個(gè)err一行 // // EOF // io: read/write on closed pipe // multiple Read calls return no data or error // short buffer // dial tcp: lookup invalid.address: no such host // remove /path/to/nonexistent/file: no such file or directory fmt.Println(errors.Unwrap(e)) // nil fmt.Println(errors.Is(e, e6)) //true fmt.Println(errors.Is(e, e3)) // true fmt.Println(errors.Is(e, e1)) // true
你可以使用Is
判斷是否包含某個(gè) error,或者使用As
提取出目標(biāo) error。
參考資料
[1]
#51945: https://github.com/golang/go/issues/51945
以上就是Go 1.13中errors包的新變化示例解析的詳細(xì)內(nèi)容,更多關(guān)于Go1.13 errors包變化的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go語(yǔ)言學(xué)習(xí)之將mp4通過rtmp推送流媒體服務(wù)的實(shí)現(xiàn)方法
對(duì)音視頻一直是小白,決定沉下心來,好好研究一下音視頻知識(shí),下面這篇文章主要給大家介紹了關(guān)于Go語(yǔ)言學(xué)習(xí)之將mp4通過rtmp推送流媒體服務(wù)的實(shí)現(xiàn)方法,需要的朋友可以參考下2022-12-12Golang安裝和使用protocol-buffer流程介紹
這篇文章主要介紹了Golang安裝和使用protocol-buffer過程,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-09-09Go語(yǔ)言常見錯(cuò)誤之將接口定義在實(shí)現(xiàn)方
在Go中,接口起到一個(gè)十分關(guān)鍵的角色,它們提供了一種方式來定義對(duì)象的行為,而不需要知道對(duì)象的具體實(shí)現(xiàn),一個(gè)常見的錯(cuò)誤是在實(shí)現(xiàn)方而不是使用方定義接口,本文將詳細(xì)探討為何這樣做是一個(gè)錯(cuò)誤,以及如何避免它2024-01-01Go并發(fā):使用sync.WaitGroup實(shí)現(xiàn)協(xié)程同步方式
這篇文章主要介紹了Go并發(fā):使用sync.WaitGroup實(shí)現(xiàn)協(xié)程同步方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-05-05Go語(yǔ)言字符串操作指南:簡(jiǎn)單易懂的實(shí)戰(zhàn)技巧
本文將介紹Go語(yǔ)言中字符串的實(shí)戰(zhàn)操作,通過本文的學(xué)習(xí),讀者將掌握Go語(yǔ)言中字符串的常用操作,為實(shí)際開發(fā)提供幫助,需要的朋友可以參考下2023-10-10