Go 1.13中errors包的新變化示例解析
Go 1.13 中 errors 包有了一些變化
這些變化是為了更好地支持 Go 的錯(cuò)誤處理提案。Go 1.20 中也增加了一個(gè)新方法,這個(gè)新方法可以代替第三方的庫處理多個(gè) error,這篇文章將介紹這些變化。
因?yàn)樵瓉淼?Go 的 errors 中的內(nèi)容非常的簡單,可能會導(dǎo)致大家輕視這個(gè)包,對于新的變化不是那么的關(guān)注。讓我們一一介紹這些新的方法。
Unwrap
如果一個(gè) err 實(shí)現(xiàn)了Unwrap函數(shù),那么errors.Unwrap會返回這個(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í)候,會采用深度優(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會把這個(gè) error 賦值給pathError變量,并且返回 true,否則返回 false。 我們這個(gè)例子正好制造的就是文件不存在的 error,所以它會輸出: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)該可以簡化成如下的方式:
func As[T error](err error "T error") (T, bool)
但是,Go 不會修改這個(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
}寫段測試代碼,我們可以看到它的效果:
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 情況,先前,有很多的第三方庫可以供我們使用,比如
go.uber.org/multierr
github.com/hashicorp/go-multierror
github.com/cockroachdb/errors
但是現(xiàn)在,你不用再造輪子或者使用第三方庫了,因?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包變化的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go語言學(xué)習(xí)之將mp4通過rtmp推送流媒體服務(wù)的實(shí)現(xiàn)方法
對音視頻一直是小白,決定沉下心來,好好研究一下音視頻知識,下面這篇文章主要給大家介紹了關(guān)于Go語言學(xué)習(xí)之將mp4通過rtmp推送流媒體服務(wù)的實(shí)現(xiàn)方法,需要的朋友可以參考下2022-12-12
Golang安裝和使用protocol-buffer流程介紹
這篇文章主要介紹了Golang安裝和使用protocol-buffer過程,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-09-09
Go語言常見錯(cuò)誤之將接口定義在實(shí)現(xiàn)方
在Go中,接口起到一個(gè)十分關(guān)鍵的角色,它們提供了一種方式來定義對象的行為,而不需要知道對象的具體實(shí)現(xiàn),一個(gè)常見的錯(cuò)誤是在實(shí)現(xiàn)方而不是使用方定義接口,本文將詳細(xì)探討為何這樣做是一個(gè)錯(cuò)誤,以及如何避免它2024-01-01
Go并發(fā):使用sync.WaitGroup實(shí)現(xiàn)協(xié)程同步方式
這篇文章主要介紹了Go并發(fā):使用sync.WaitGroup實(shí)現(xiàn)協(xié)程同步方式,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-05-05
Go語言字符串操作指南:簡單易懂的實(shí)戰(zhàn)技巧
本文將介紹Go語言中字符串的實(shí)戰(zhàn)操作,通過本文的學(xué)習(xí),讀者將掌握Go語言中字符串的常用操作,為實(shí)際開發(fā)提供幫助,需要的朋友可以參考下2023-10-10

