How is Go Error nesting implemented?

Go Error's design philosophy is "Errors Are Values".

How should this sentence be understood? It's difficult to translate. However, from the perspective of source code, it seems easier to understand the meaning behind it.

The source code of Go Error is very simple, just a few lines:

// src/builtin/builtin.go

type error interface {
	Error() string
}
Copy code

Error is an interface type. You only need to implement the Error() method. In the Error() method, you can return any content of the custom structure.

First, let's talk about how to create an error.

Create Error
There are two ways to create an error:

errors.New();
fmt.Errorf().
errors.New()
errors. The use of New() continues the consistent style of Go. Just click New.

For example:

package main

import (
	"errors"
	"fmt"
)

func main() {
	err := errors.New("This is errors.New() Error creating")
	fmt.Printf("err Error type:%T,The error is:%v\n", err, err)
}

/* output
err Error type: * errors Errorstring, the error is: This is errors Error creating new()
*/
Copy code

The only thing confusing about this code may be the wrong type, but it doesn't matter. Just look at the source code and it will be solved in an instant.

The source code is as follows:

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

You can see that errorString is a structure that implements the Error() method, and the New function directly returns the errorString pointer.

This usage is simple, but not practical. If I still want to return the context information of the program, it has no way.

Let's look at the second way.

fmt.Errorf()
Let's start with an example:

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")
	}
}

/* output
data not found, sql: no rows in result set
*/
Copy code

This example outputs the results we want, but it's not enough.

In general, we will use FMT The errorf() function adds the text information we want to add to make the returned content clearer and more flexible.

Therefore, the foo() function will be changed as follows:

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

Then the problem appears, after FMT In the encapsulation of errorf(), the original error type is changed, resulting in err = = SQL Errnorows is no longer valid, and the returned information becomes Unknown error.

If you want to do different processing according to the returned error type, you can't implement it.

Therefore, Go 1.13 provides us with wrapError to deal with this problem.

Wrap Error
Take an example:

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
*/
Copy code

At first glance, it seems that there is no difference between good and bad, but the implementation principle behind it is not the same.

Go extends FMT Errorf() function, adding an%w identifier to create 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
}
Copy code

When w% is used, the function will return & wrapError {s, p.wrappederr}. The wrapError structure is defined as follows:

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

The Error() method is implemented, indicating that it is an error, and the Unwrap() method is to obtain the encapsulated error.

// src/errors/wrap.go

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

The relationship between them is as follows:

Therefore, we can use w% to transform the above program to enrich its content output.

As follows:

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
*/
Copy code

Finally, we have a satisfactory output. Each function adds the necessary context information, and it also conforms to the judgment of the error type.

errors. The is () function is used to determine whether the err and its encapsulated error chain contain the target type. This also solves the problem of being unable to judge the error type proposed above.

last
If you think this article is a little helpful to you, give it a compliment. Or you can join my development exchange group: 1025263163 learn from each other, and we will have professional technical Q & A to solve doubts

If you think this article is useful to you, please click star: http://github.crmeb.net/u/defu esteem it a favor!

PHP learning manual: https://doc.crmeb.com
Technical exchange forum: https://q.crmeb.com

Keywords: Go Back-end

Added by emceej on Sun, 16 Jan 2022 04:54:33 +0200