Go 1.13中errors包的新变化示例解析

 更新时间:2023年12月20日 10:00:14   作者:晁岳攀(鸟窝) 鸟窝聊技术  
这篇文章主要为大家介绍了Go 1.13中errors包的新变化示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

Go 1.13 中 errors 包有了一些变化

这些变化是为了更好地支持 Go 的错误处理提案。Go 1.20 中也增加了一个新方法,这个新方法可以代替第三方的库处理多个 error,这篇文章将介绍这些变化。

因为原来的 Go 的 errors 中的内容非常的简单,可能会导致大家轻视这个包,对于新的变化不是那么的关注。让我们一一介绍这些新的方法。

Unwrap

如果一个 err 实现了Unwrap函数,那么errors.Unwrap会返回这个 err 的unwrap方法的结果,否则返回 nil。 一般标准的 error 都没有实现Unwrap方法,比如io.EOF, 但是也有一小部分的 error 实现了Unwrap方法,比如os.PathErroros.LinkErroros.SyscallErrornet.OpErrornet.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,它实现了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,它包含了e1io.ErrClosedPipe两个 error。

我们常常在多层调用的时候,把最底层的 error 逐层包装传递上去,这个时候我们可以使用fmt.Errorf + %w包装 error。 在最高层处理 error 的时候,再逐层Unwrap解开 error,逐层处理。

Is

Is函数检查 error 的树中是否包含指定的目标 error。

啥是 error 的? 一个 error 的数包括它本身,以及通过Unwrap方法逐层解开的 error。 error 的Unwrap方法的返回值,可能是单个 error,也可能是是多个 error,在返回多个 error 的时候,会采用深度优先的方式进行遍历检查,寻找目标 error。

怎么才算找到目标 error 呢?一种情况就是此 err 就是目标 error,这没有什么好说的,第二种就是此 err 实现了Is(err)方法,把目标 err 扔进Is方法返回 true。

所以从功能上看Is函数其实叫做Has函数更贴切些。

下面是一个例子:

    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 的数,检查是否包含目标 error。As是遍历 error 的数,检查每一个 error,看看是否可以把从 error 赋值给目标变量,如果是,则返回 true,并且目标变量已赋值,否则返回 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

经常常犯的一个错误就是我们使用一个error变量作为As的第二个参数。下面这个例子 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函数,在 Go 支持泛型之后,As应该可以简化成如下的方式:

func As[T error](err error "T error") (T, bool)

但是,Go 不会修改这个导致不兼容的 API,所以我们只能继续保留As函数,增加一个新的函数是一个可行的方法,无论它叫做IsAAsOf还是AsTarget或者其他。

如果你已经掌握了 Go 的泛型,你可以自己实现一个As函数,比如下面的代码:

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。 当然,为了处理多个 errors 情况,先前,有很多的第三方库可以供我们使用,比如

go.uber.org/multierr

github.com/hashicorp/go-multierror

github.com/cockroachdb/errors

但是现在,你不用再造轮子或者使用第三方库了,因为 Go 1.20 中增加了errors.Join函数,它可以把多个 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提取出目标 error。

参考资料

[1]

#51945: https://github.com/golang/go/issues/51945

以上就是Go 1.13中errors包的新变化示例解析的详细内容,更多关于Go1.13 errors包变化的资料请关注脚本之家其它相关文章!

相关文章

  • Go实现凯撒密码加密解密

    Go实现凯撒密码加密解密

    这篇文章主要为大家介绍了Go实现凯撒密码加密解密示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • Go语言ants协程池的具体使用

    Go语言ants协程池的具体使用

    ants是Go语言中一款高效的协程池库,通过复用协程资源优化高并发场景下的性能,本文就来介绍一下golang中大名鼎鼎的ants协程池库的实现原理,感兴趣的可以了解一下
    2025-08-08
  • Golang的strings.Split()踩坑记录

    Golang的strings.Split()踩坑记录

    工作中,当我们需要对字符串按照某个字符串切分成字符串数组数时,常用到strings.Split(),本文主要介绍了Golang的strings.Split()踩坑记录,感兴趣的可以了解一下
    2022-05-05
  • Golang实现简单http服务器的示例详解

    Golang实现简单http服务器的示例详解

    这篇文章主要为大家详细介绍了如何利用Golang实现简单http服务器,文中的示例代码讲解详细,对我们学习Golang有一定的帮助,需要的可以参考一下
    2023-03-03
  • 深入浅出Go语言:手把手教你高效生成与解析JSON数据

    深入浅出Go语言:手把手教你高效生成与解析JSON数据

    本文将带你一步步走进Go语言的世界,教你如何高效生成与解析JSON数据,无论你是初学者还是经验丰富的开发者,都能在本文中找到实用的技巧和灵感,本文内容简洁明了,示例丰富,让你在阅读的过程中轻松掌握Go语言生成与解析JSON数据的技巧,需要的朋友可以参考下
    2024-02-02
  • Golang timer可能造成的内存泄漏问题分析

    Golang timer可能造成的内存泄漏问题分析

    本文探讨了Golang中timer可能造成的内存泄漏问题,通过分析一段代码,解释了为什么协程在调用timer.Stop()后无法正常退出,文章指出,timer.Stop()并不关闭Channel,导致协程无法继续执行,最后,提出了一种修复方法,并呼吁大家关注和分享
    2024-12-12
  • Go集成swagger实现在线接口文档的教程指南

    Go集成swagger实现在线接口文档的教程指南

    wagger是一个用于设计,构建和文档化API的开源框架,在Go语言中,Swagger可以帮助后端开发人员快速创建和定义RESTful API,并提供自动生成接口文档的功能,所以本文给大家介绍了Go集成swagger实现在线接口文档的方法,需要的朋友可以参考下
    2024-11-11
  • Go语言中io包核心接口示例详解

    Go语言中io包核心接口示例详解

    Go的io包提供了io.Reader和io.Writer接口,分别用于数据的输入和输出,下面这篇文章主要给大家介绍了关于Go语言中io包核心接口的相关资料,需要的朋友可以参考下
    2021-12-12
  • Go语言实现AzDG可逆加密算法实例

    Go语言实现AzDG可逆加密算法实例

    这篇文章主要介绍了Go语言实现AzDG可逆加密算法,实例分析了AzDG可逆加密算法的实现技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-02-02
  • Golang设计模式之组合模式讲解

    Golang设计模式之组合模式讲解

    这篇文章主要介绍了Golang设计模式之组合模式,组合模式针对于特定场景,如文件管理、组织管理等,使用该模式能简化管理,使代码变得非常简洁
    2023-01-01

最新评论