引言

go 包实现了处理 error 的一些功能。

这是源码注释

// Package errors implements functions to manipulate errors.

errors 包的源码放在 $GOROOT/src/errors

查看安装目录

go env GOROOT

为了方便阅读源码和调试,建议将源码复制后作为新项目打开

cp -r errors ~/

文件

errors 包有两个文件,errors.go 和 wrap.go 以及三个测试文件。

errors.go

errors.go 中有一个 errorString 结构体

// errorString is a trivial implementation of error.
type errorString struct {
	s string
}

读注释我们明白,这个结构体是 error 的简单实现。我们可以自定义错误的类型,而这个结构体就是源码中对错误的一个简单实现。

然后有这个结构体的 constructor,即 New 函数

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

我们发现返回一个 error, 继续读源码,我们知道这是一个接口,在 builtin 包 builtin.go 文件中, builtin 包用来给有预定义标识符生成文档,也就是说这个包里都是一些预定义标识符,这些标识符的具体实现没有在这个包里,写这个包只是为了使用 goget 生成文档。

// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
	Error() string
}

这个接口是用来表示错误状态的,只要实现了 Error() 方法,那么就实现了这个接口。也就是说,我们可以自定义一个类型,然后实现 Error() 方法,这个类型就是我们自定义的错误类型。当这个接口为 nil 时,没有错误,所以我们一般来判断这个接口是否为空,来判断是否出错。

func main() {
	if err := A(); err != nil {
	//
	}
}

余下的 Error() 函数就是 errorString 的方法,所以 errorString 实现了 error 接口,所以 errorStirng 可以转换为 error 类型,所以才返回 errorString 类型,而函数签名处是 error 类型。

我们可以看 fmt 包 errors.go 文件中,就自定义了一个错误类型

type wrapError struct {
	msg string
	err error
}

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

从上面的代码我们可以知道,errors.go 文件主要就是 围绕 error 接口展开的,接下来我们来看测试文件中具体的例子。

example_test.go

自定义一个类型

// MyError is an error implementation that includes a time and message.
type MyError struct {
	When time.Time
	What string
}

然后实现 error 接口

func (e MyError) Error() string {
	return fmt.Sprintf("%v: %v", e.When, e.What)
}

这里使用了 opps 函数封装了一下,我们可以模仿 errors.go 中把它改为 constructor,即 New 函数。

func New(s string) error {
	return &MyError{time.Date(1989, 3, 15, 22, 30, 0, 0, time.UTC), s}
}
func main() {
	if err := New("haha"); err != nil {
		fmt.Println(err)
	}
}

可得 1989-03-15 22:30:00 +0000 UTC: haha,这个即我们定义的 error 类型的具体实现。

errors_test.go

  • ExampleNew()

有测试 New 函数的,即新生成一个 error 类型,这个类型的格式只有一个 string.

func ExampleNew() {
	err := errors.New("emit macho dwarf: elf header corrupted")
	if err != nil {
		fmt.Print(err)
	}
	// Output: emit macho dwarf: elf header corrupted
}
  • TestErrorMethod()

有测试方法的,我们生成一个 error 类型后,这个类型实现了 error() 方法

func TestErrorMethod(t *testing.T) {
	err := errors.New("abc")
	if err.Error() != "abc" {
		t.Errorf(`New("abc").Error() = %q, want %q`, err.Error(), "abc")
	}
}

这里容易搞混 err 和 err.Error(), 不知道它们有什么区别,这里我们来看一下

func main() {
	err := errors.New("sdf")

	fmt.Println(reflect.TypeOf(err)) //*errors.errorString
	fmt.Println(reflect.ValueOf(err)) //sdf

	e := err.Error()
	fmt.Println(reflect.TypeOf(e))  //string
	fmt.Println(reflect.ValueOf(e)) //sdf
}

New 函数返回一个 error 接口的实例,这个接口是 errorString 的实现,所以 err 是个接口,它的动态类型是 *errors.errorString, 值是实例传入的 errorString 的值 sdf.

func New(text string) error {
	return &errorString{text}
}

err 是个接口,error() 就是 err 接口方法集中的 error() 方法,所以这里 err.Error() 就是调用 Error() 方法,把返回值赋给 e,返回值是 string, 所以 e 的类型是 string

type error interface {
	Error() string
}

而 e 是实例 err 创建出来的,err 接口这里是 errorString 实现的,所以这里就是传入的 sdf,sdf 赋给 errorString 的 是,Error() 再获取 s, 所以 e 的 值就是 sdf

  • ExampleNew_errorf()

有 格式化新建 error 的,即 ExampleNew_errorf 函数,这个函数用了 fmt 包中的 Errorf 函数,这个函数用来用来新建格式化 error,用到了自建的一种格式,即 wrapError struct, 也就是我们在 error 自建类型中讲到的,不过这里不具体展开其实现原理。

// The fmt package's Errorf function lets us use the package's formatting
// features to create descriptive error messages.
func ExampleNew_errorf() {
	const name, id = "bimmler", 17
	err := fmt.Errorf("user %q (id %d) not found", name, id)
	if err != nil {
		fmt.Print(err)
	}
	// Output: user "bimmler" (id 17) not found
}
  • TestNewEqual()

有关于 New 函数特点的,我们再来看一下 New 函数的源码

func New(text string) error {
	return &errorString{text}
}

它返回时,建立了一个临时变量 errorString 类型,而我们在区分 err 和 error 时知道,New 函数返回时,返回了一个指针,如果忘记了,可以运行

func main() {
	fmt.Println(reflect.TypeOf(errors.New("sdf")))
}

所以如果直接比较两个 errors.New(), 哪怕输入的字符串相同,它们也是不相同的两个变量

func main() {
	fmt.Println(errors.New("f") == errors.New("f"))
}

所以这里是 flase, 我们就不能这样判断错误是否相同,我们可以先赋值给一个变量

func main() {
	err := errors.New("df")
	fmt.Println(err == err)
}

这里 err 是一个变量,所以是 true, 或者我们可以通过 Error() 方法获取它们的值,再比较

func main() {
	fmt.Println(errors.New("f").Error() == errors.New("f").Error())
}

故是 true

wrap.go

wrap_test.go