SoFunction
Updated on 2025-03-05

Analysis of new changes in errors package in Go 1.13

There have been some changes in the errors package in Go 1.13

These changes are intended to better support Go's error handling proposals. A new method has also been added in Go 1.20, which can handle multiple errors instead of third-party libraries, and this article will introduce these changes.

Because the content in the original Go errors is very simple, it may cause everyone to despise this package and are not so concerned about new changes. Let's introduce these new methods one by one.

Unwrap

If an err is implementedUnwrapFunction, thenWill return this errunwrapThe result of the method, otherwise return nil. No general standard error is implementedUnwrapMethods, such as, but a small number of errors have been implementedUnwrapMethods, such asetc.

For example, the following code:

 (()) // nil
 _, err := ("tcp", ":80")
 ((err))

The first line becauseNoUnwrapmethod, so output nil. The failed return err is*, It implementsUnwrapMethod, return to the underlying*, so the output of the second line islookup : no such host

The most commonly used ones we use + %wWrapping an error, such as the following code:

 e1 := ("e1: %w", )
 e2 := ("e2: %w + %w", e1, )
 e3 := ("e3: %w", e2)
 e4 := ("e4: %w", e3)
 ((e4)) // e3: e2: e1: EOF + io: read/write on closed pipe

This code is wrapped layer by layer, and the finale4All errors are included, we can useUnpack the package layer by layer until the bottom layer error. You can wrap multiple errors at once, such as the abovee2, it containse1andTwo errors.

We often pass the lowest error layer by layer when calling multiple layers, and we can use it + %wPacking error. When processing errors at the highest level, then layer by layerUnwrapUnlock the error and process it layer by layer.

Is

IsThe function checks whether the tree of the error contains the specified target error.

What is an errorTree? The number of an error includes itself, and throughUnwrapThe error that unravels the method layer by layer. errorUnwrapThe return value of the method may be a single error or multiple errors. When multiple errors are returned, the depth-first way will be used to traverse and check to find the target error.

How can I find the target error? One situation is that this err is the target error. There is nothing to say about this, and the second is that this err is implemented.Is(err)Method: throw target err intoIsThe method returns true.

So from the perspective of functionIsThe function is actually calledHasThe function is more appropriate.

Here is an example:

    e1 := ("e1: %w", )
    e2 := ("e2: %w + %w", e1, )
    e3 := ("e3: %w", e2)
    e4 := ("e4: %w", e3)
    ((e4, )) // true
    ((e4, )) // true
    ((e4, )) // false

As

Isis the number of traversal errors to check whether the target error is included.AsIt is to traverse the number of errors, check each error, and see if it can be assigned from error to the target variable. If so, it will return true, and the target variable has been assigned, otherwise it will return false.

We can see the following exampleAsUsage:

 if _, err := ("non-existing"); err != nil {
  var pathError *
  if (err, &pathError) {
   ("failed at path:", )
  } else {
   (err)
  }
 }

If the returned error tree contains*,SoWill assign this error topathErrorvariable, and returns true, otherwise returns false. Our example just creates an error whose file does not exist, so it will output:failed at path: non-existing

A common mistake is that we use oneerrorVariable asAsThe second parameter of . The following example tmp is the error interface type, so origin can be assigned directly to tmp, soReturns true, and the value of tmp is the value of origin.

 var origin = ("error: %w", )
 var tmp = 
 if (origin, &tmp) {
  (tmp) // error: EOF
 }

AsIt is always so awkward to use. You have to declare a variable every time and then pass this variable toAsFunctions, after Go supports generics,AsIt should be simplified into the following method:

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

However, Go will not modify this API that causes incompatible, so we can only keep itAsFunction, adding a new function is a feasible method, no matter what it is calledIsAAsOfstillAsTargetOr something else.

If you have mastered the generics of Go, you can implement one by yourselfAsFunctions, such as the following code:

func AsA[T error](err error "T error") (T, bool) {
 var isErr T
 if (err, &isErr) {
  return isErr, true
 }
 var zero T
 return zero, false
}

Write a piece of test code and we can see its effect:

type MyError struct{}
func (*MyError) Error() string { return "MyError" }
func main() {
 var err error = ("error: %w", &MyError{})
 m, ok := AsA[*MyError](err "*MyError") // MyError does not implement error (Error method has pointer receiver)
 (m, ok)
}

Everyone discussed it in #51945[1] for a while, but it ended in vain again.

Join

In our project, we sometimes need to deal with multiple errors, such as the following code:

func (s *Server) Serve() error {
    var errs []error
    if err := (); err != nil {
        errs = append(errs, err)
    }
    if err := (); err != nil {
        errs = append(errs, err)
    }
    if err := (); err != nil {
        errs = append(errs, err)
    }
    if len(errs) > 0 {
        return ("server error: %v", errs)
    }
    return nil
}

In this code, we need to deal with three errors. If one error is not nil, then we return errors. Of course, in order to handle multiple errors, there were many third-party libraries available for us to use, for example

/multierr

/hashicorp/go-multierror

/cockroachdb/errors

But now, you don't have to build wheels or use third-party libraries, because it's added in Go 1.20Function, it can combine multiple errors into one error, such as the following code:

 var e1 = 
 var e2 = 
 var e3 = 
 var e4 = 
 _, e5 := ("tcp", ":80")
 e6 := ("/path/to/nonexistent/file")
 var e = (e1, e2)
 e = (e, e3)
 e = (e, e4)
 e = (e, e5)
 e = (e, e6)
 (())
    // The output is as follows, each err is one line    //
 // EOF
 // io: read/write on closed pipe
 // multiple Read calls return no data or error
 // short buffer
 // dial tcp: lookup : no such host
 // remove /path/to/nonexistent/file: no such file or directory
 ((e)) // nil
 ((e, e6)) //true
 ((e, e3)) // true
 ((e, e1)) // true

You can useIsDetermine whether an error is included or usedAsExtract the target error.

References

[1]

#51945: /golang/go/issues/51945

The above is the detailed analysis of the new changes of the errors package in Go 1.13. For more information about the changes of the Go1.13 errors package, please follow my other related articles!