关于golang:Go-Error-嵌套到底是怎么实现的

11次阅读

共计 3462 个字符,预计需要花费 9 分钟才能阅读完成。

原文链接: Go Error 嵌套到底是怎么实现的?

Go Error 的设计哲学是「Errors Are Values」。

这句话应该怎么了解呢?翻译起来挺难的。不过从源码的角度来看,如同更容易了解其背地的含意。

Go Error 源码很简略,寥寥几行:

// src/builtin/builtin.go

type error interface {Error() string
}

error 是一个接口类型,只须要实现 Error() 办法即可。在 Error() 办法中,就能够返回自定义构造体的任意内容。

上面首先说说如何创立 error

创立 Error

创立 error 有两种形式,别离是:

  1. errors.New()
  2. fmt.Errorf()

errors.New()

errors.New() 的应用连续了 Go 的一贯格调,New 一下就能够了。

举一个例子:

package main

import (
    "errors"
    "fmt"
)

func main() {err := errors.New("这是 errors.New() 创立的谬误")
    fmt.Printf("err 谬误类型:%T,谬误为:%v\n", err, err)
}

/* 输入
err 谬误类型:*errors.errorString,谬误为:这是 errors.New() 创立的谬误
*/

这段代码惟一让人困惑的中央可能就是谬误类型了,但没关系。只有看一下源码,就霎时迎刃而解。

源码如下:

// src/errors/errors.go

// 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}

能够看到,errorString 是一个构造体,实现了 Error() 办法,New 函数间接返回 errorString 指针。

这种用法很简略,但不实用。如果我还想返回程序的上下文信息,它就没辙了。

上面看第二种形式。

fmt.Errorf()

还是先看一个例子:

package main

import (
    "database/sql"
    "fmt"
)

func foo() error {return sql.ErrNoRows}

func bar() error {return foo()
}

func main() {err := bar()
    if err == sql.ErrNoRows {fmt.Printf("data not found, %+v\n", err)
        return
    }
    if err != nil {fmt.Println("Unknown error")
    }
}

/* 输入
data not found, sql: no rows in result set
*/

这个例子输入了咱们想要的后果,然而还不够。

个别状况下,咱们会通过应用 fmt.Errorf() 函数,附加上咱们想增加的文本信息,使返回内容更明确,解决起来更灵便。

所以,foo() 函数会改成上面这样:

func foo() error {return fmt.Errorf("foo err, %v", sql.ErrNoRows)
}

这时问题就呈现了,通过 fmt.Errorf() 的封装,原始 error 类型产生了扭转,这就导致 err == sql.ErrNoRows 不再成立,返回信息变成了 Unknown error

如果想依据返回的 error 类型做不同解决,就无奈实现了。

因而,Go 1.13 为咱们提供了 wrapError 来解决这个问题。

Wrap Error

看一个例子:

package main

import ("fmt")

type myError struct{}

func (e myError) Error() string {return "Error happended"}

func main() {e1 := myError{}
    e2 := fmt.Errorf("E2: %w", e1)
    e3 := fmt.Errorf("E3: %w", e2)
    fmt.Println(e2)
    fmt.Println(e3)
}

/* output
E2: Error happended
E3: E2: Error happended
*/

乍一看如同好没什么区别,但背地的实现原理却并不相同。

Go 扩大了 fmt.Errorf() 函数,减少了一个 %w 标识符来创立 wrapError

// src/fmt/errors.go

func Errorf(format string, a ...interface{}) error {p := newPrinter()
    p.wrapErrs = true
    p.doPrintf(format, a)
    s := string(p.buf)
    var err error
    if p.wrappedErr == nil {err = errors.New(s)
    } else {err = &wrapError{s, p.wrappedErr}
    }
    p.free()
    return err
}

当应用 w% 时,函数会返回 &wrapError{s, p.wrappedErr}wrapError 构造体定义如下:

// src/fmt/errors.go

type wrapError struct {
    msg string
    err error
}

func (e *wrapError) Error() string {return e.msg}

func (e *wrapError) Unwrap() error {return e.err}

实现了 Error() 办法,阐明它是一个 error,而 Unwrap() 办法是为了获取被封装的 error

// src/errors/wrap.go

func Unwrap(err error) error {
    u, ok := err.(interface {Unwrap() error
    })
    if !ok {return nil}
    return u.Unwrap()}

它们之间的关系是这样的:

因而,咱们能够应用 w% 将上文中的程序进行革新,使其内容输入更丰盛。

如下:

package main

import (
    "database/sql"
    "errors"
    "fmt"
)

func bar() error {if err := foo(); err != nil {return fmt.Errorf("bar failed: %w", foo())
    }
    return nil
}

func foo() error {return fmt.Errorf("foo failed: %w", sql.ErrNoRows)
}

func main() {err := bar()
    if errors.Is(err, sql.ErrNoRows) {fmt.Printf("data not found,  %+v\n", err)
        return
    }
    if err != nil {fmt.Println("Unknown error")
    }
}

/* output
data not found,  bar failed: foo failed: sql: no rows in result set
*/

终于有了让人称心的输入后果,每个函数都减少了必要的上下文信息,而且也合乎对谬误类型的判断。

errors.Is() 函数用来判断 err 以及其封装的 error 链中是否蕴含指标类型。这也就解决了上文提出的无奈判断谬误类型的问题。

后记

其实,Go 目前对 Error 的解决形式也是充斥争议的。不过,官网团队正在踊跃和社区交换,提出改良办法。置信在不久的未来,肯定会找到更好的解决方案。

现阶段来说,大部分团队可能会抉择 github.com/pkg/errors 包来进行错误处理。如果感兴趣的话,能够学学看。

好了,本文就到这里吧。 关注我,带你通过问题读 Go 源码。


源码地址:

  • https://github.com/yongxinz/gopher

举荐浏览:

  • 为什么要防止在 Go 中应用 ioutil.ReadAll?
  • [如何在 Go 中将 []byte 转换为 io.Reader?](https://mp.weixin.qq.com/s/nF…)
  • 开始读 Go 源码了

参考文章:

  • https://chasecs.github.io/pos…
  • https://medium.com/@dche423/g…
  • https://www.flysnow.org/2019/…
正文完
 0