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