Go 1.13中errors包的新變化示例解析
Go 1.13 中 errors 包有了一些變化
這些變化是為了更好地支持 Go 的錯誤處理提案。Go 1.20 中也增加了一個新方法,這個新方法可以代替第三方的庫處理多個 error,這篇文章將介紹這些變化。
因為原來的 Go 的 errors 中的內(nèi)容非常的簡單,可能會導(dǎo)致大家輕視這個包,對于新的變化不是那么的關(guān)注。讓我們一一介紹這些新的方法。
Unwrap
如果一個 err 實現(xiàn)了Unwrap
函數(shù),那么errors.Unwrap
會返回這個 err 的unwrap
方法的結(jié)果,否則返回 nil。 一般標(biāo)準(zhǔn)的 error 都沒有實現(xiàn)Unwrap
方法,比如io.EOF
, 但是也有一小部分的 error 實現(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))
第一行因為io.EOF
沒有Unwrap
方法,所以輸出 nil。 net.Dial 失敗返回的 err 是*net.OpError
,它實現(xiàn)了Unwrap
方法,返回更底層的*net.DNSError
,所以第二行輸出為lookup invalid.address: no such host
。
最常用的,我們使用fmt.Errorf
+ %w
包裝一個 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
這段代碼逐層進行了包裝,最后的e4
包含了所有的 error,我們可以通過errors.Unwrap
逐層進行解包,直到最底層的 error。 fmt.Errorf 可以 1 一次包裝多個 error,比如上面的e2
,它包含了e1
和io.ErrClosedPipe
兩個 error。
我們常常在多層調(diào)用的時候,把最底層的 error 逐層包裝傳遞上去,這個時候我們可以使用fmt.Errorf
+ %w
包裝 error。 在最高層處理 error 的時候,再逐層Unwrap
解開 error,逐層處理。
Is
Is
函數(shù)檢查 error 的樹中是否包含指定的目標(biāo) error。
啥是 error 的樹? 一個 error 的數(shù)包括它本身,以及通過Unwrap
方法逐層解開的 error。 error 的Unwrap
方法的返回值,可能是單個 error,也可能是是多個 error,在返回多個 error 的時候,會采用深度優(yōu)先的方式進行遍歷檢查,尋找目標(biāo) error。
怎么才算找到目標(biāo) error 呢?一種情況就是此 err 就是目標(biāo) error,這沒有什么好說的,第二種就是此 err 實現(xiàn)了Is(err)
方法,把目標(biāo) err 扔進Is
方法返回 true。
所以從功能上看Is
函數(shù)其實叫做Has
函數(shù)更貼切些。
下面是一個例子:
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ù),檢查每一個 error,看看是否可以把從 error 賦值給目標(biāo)變量,如果是,則返回 true,并且目標(biāo)變量已賦值,否則返回 false。
下面這個例子,我們可以看到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
會把這個 error 賦值給pathError
變量,并且返回 true,否則返回 false。 我們這個例子正好制造的就是文件不存在的 error,所以它會輸出:failed at path: non-existing
經(jīng)常常犯的一個錯誤就是我們使用一個error
變量作為As
的第二個參數(shù)。下面這個例子 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
使用起來總是那么別別扭扭,每次總得聲明一個變量,然后把這個變量傳遞給As
函數(shù),在 Go 支持泛型之后,As
應(yīng)該可以簡化成如下的方式:
func As[T error](err error "T error") (T, bool)
但是,Go 不會修改這個導(dǎo)致不兼容的 API,所以我們只能繼續(xù)保留As
函數(shù),增加一個新的函數(shù)是一個可行的方法,無論它叫做IsA
、AsOf
還是AsTarget
或者其他。
如果你已經(jīng)掌握了 Go 的泛型,你可以自己實現(xiàn)一個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 }
寫段測試代碼,我們可以看到它的效果:
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]討論了一段時間,又是無疾而終了。
Join
在我們的項目中,有時候需要處理多個 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 }
這段代碼中,我們需要處理三個 error,如果有一個 error 不為 nil,那么我們就返回 errs。 當(dāng)然,為了處理多個 errors 情況,先前,有很多的第三方庫可以供我們使用,比如
go.uber.org/multierr
github.com/hashicorp/go-multierror
github.com/cockroachdb/errors
但是現(xiàn)在,你不用再造輪子或者使用第三方庫了,因為 Go 1.20 中增加了errors.Join
函數(shù),它可以把多個 error 合并成一個 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()) // 輸出如下,每一個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
判斷是否包含某個 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包變化的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go語言學(xué)習(xí)之將mp4通過rtmp推送流媒體服務(wù)的實現(xiàn)方法
對音視頻一直是小白,決定沉下心來,好好研究一下音視頻知識,下面這篇文章主要給大家介紹了關(guān)于Go語言學(xué)習(xí)之將mp4通過rtmp推送流媒體服務(wù)的實現(xiàn)方法,需要的朋友可以參考下2022-12-12Golang安裝和使用protocol-buffer流程介紹
這篇文章主要介紹了Golang安裝和使用protocol-buffer過程,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-09-09Go并發(fā):使用sync.WaitGroup實現(xiàn)協(xié)程同步方式
這篇文章主要介紹了Go并發(fā):使用sync.WaitGroup實現(xiàn)協(xié)程同步方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-05-05