Home » Dealing with Errors in Go. Errors in Go could be cumbersome and… | by David Dymko | Jun, 2023

Dealing with Errors in Go. Errors in Go could be cumbersome and… | by David Dymko | Jun, 2023

by Icecream
0 comment

Errors in Go could be cumbersome and verbose

Photo by David Pupăză on Unsplash

Go has a novel stance on errors by treating them as values. There aren’t any such constructs as exceptions within the language, though you could deal with errors on this method. Dealing with errors can generally really feel cumbersome and verbose. However, in Go 1.13 the introduction of Wrapping and Unwrapping errors was added to the language. This extends errors to embed errors inside themselves, making a nest of errors that you may work together with.

Wrapping errors lets you add extra context round your error messages. This is achieved by embedding error sorts or error objects inside your errors. This permits for larger flexibility (and discount of code, as you’ll later see) to test if the error you acquired of a particular kind/object without having to parse the error message.

Creating a wrapped error could be achieved by calling fmt.Errorf() and utilizing %w inside.

fmt.Errorf("error %s: %w", "my error", err)

A fast instance of wrapping an error after which unwrapping it

package deal foremost

import (
"errors"
"fmt"
)

func foremost() {
var errrorInner = errors.New("internal error")
wrapped := fmt.Errorf("outer error: %w", errrorInner)
// Prints out the whole error message
fmt.Println(wrapped)
// Prints out the wrapped portion of the error message which is %w
fmt.Println(errors.Unwrap(wrapped))
// Prints out nil as we solely wrapped a single error
fmt.Println(errors.Unwrap(errors.Unwrap(wrapped)))
}

Output:

outer error: internal error
internal error
<nil>

You can see that there are two components to the error once we wrap/unwrap it.

  • the preliminary error layer: outer message: %w
  • the embedded %w layer: internal error

It can be attainable to test equality if the wrapped error matches a particular error by doing one thing corresponding to this.

package deal foremost

import (
"errors"
"fmt"
)

func foremost() {
var errrorInner = errors.New("internal error")
wrapped := fmt.Errorf("outer error: %w", errrorInner)
if wrapped == errrorInner {
fmt.Println("I will not execute")
}
if errors.Unwrap(wrapped) == errrorInner {
fmt.Println(errors.Unwrap(wrapped))
}
}

Output:

go run foremost.go
internal error

While you may test to see if a particular layer of the error matches this may be cumbersome. Imagine having a number of errors wrapped inside itself. This is the place errors.Is() and errors.Has() can be utilized to simplify your code.

The errors.Is() checks to see if any error in nest of errors matches a specified goal. This can drastically lower down the necessity to repeatedly name Unwrap and doing a comparability test you probably have nested errors.

The similar code that we had above the place we needed to unwrap and test the error could be decreased right down to the next.

package deal foremost

import (
"errors"
"fmt"
)

func foremost() {
var errrorInner = errors.New("internal error")
wrapped := fmt.Errorf("outer error: %w", errrorInner)
if errors.Is(wrapped, errrorInner) {
fmt.Println(errors.Unwrap(wrapped))
}
}

Now, you’ll nonetheless be required to unwrap the error nevertheless many occasions you might have it nested to get that particular message.

Let’s take our similar code however add one other outer layer of error after which unwrap accordingly.

package deal foremost

import (
"errors"
"fmt"
)
func foremost() {
var errrorInner = errors.New("internal error")
wrapped := fmt.Errorf("outer error: %w", errrorInner)
begin := fmt.Errorf("that is by base %w", wrapped)
fmt.Println(begin)
if errors.Is(begin, errrorInner) {
oneUnwrap := errors.Unwrap(begin)
fmt.Println(oneUnwrap)

twoUnwrap := errors.Unwrap(oneUnwrap)
fmt.Println(twoUnwrap)
}
}

Output:

go run foremost.go
that is by base outer error: internal error
outer error: internal error
internal error

While much like Is() the most important distinction right here is that Has() checks the error nesting for a particular kind . This could be seen as a cleaner approach to do kind assertion if e, okay := err.(*ErrorInner); okay

Here is an instance:

package deal foremost
import (
"errors"
"fmt"
)
kind ErrorInner struct {
msg string
}
func (e *ErrorInner) Error() string {
return fmt.Sprintf("message: %s", e.msg)
}
func foremost() {
wrapped := fmt.Errorf("outer error: %w", &ErrorInner{msg: "internal error"})
fmt.Println(wrapped)
var targetErr *ErrorInner
if errors.As(wrapped, &targetErr) {
fmt.Println(errors.Unwrap(wrapped))
}
}

Output:

go run foremost.go
outer error: message: internal error
message: internal error

In the instance the total wrapped error had included the Error() technique from ErrorInner . Then once we need to name As() we have to go in a pointer of the kind we’re in search of.

The means to wrap errors is sort of helpful. As you may have a number of “failure” states in a operate. Creating customized errors for every of these states after which having a single test with Is() or As() can drastically lower down on code. It may also assist to cleaner and clearer code as further contextual data.

Useful Links

You may also like

Leave a Comment