Go checking for the type of a custom Error

Error HandlingGo

Error Handling Problem Overview


I am having a hard time using a custom Error type in Go. I read this Blog post on Errors

So I tried this:

In my model.go I defined a custom error:

type ModelMissingError struct {
	msg string // description of error
}

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

In one of my methods I throw a custom error like this:

...
return Model{}, &ModelMissingError{"no model found for id"}
...

In the caller of that method I would like to check the error returned for its type and take action if it is in fact a ModelMissingError.

How can I do this?

I tried this:

if err == model.ModelMissingError 

The result is *type model.ModelMissingError is not an expression*

Clearly I am missing something.

Error Handling Solutions


Solution 1 - Error Handling

Reading the Blog post further exposes a bit of Go like this:

serr, ok := err.(*model.ModelMissingError)

This is the comma ok idiom, clearly I need to re do my go lang tour

Solution 2 - Error Handling

I have manged to make an error assertion using the switch statement as follows:

 err := FuncModelMissingError()

 switch t := err.(type) {
 default:
     fmt.Println("not a model missing error")
 case *ModelMissingError:
     fmt.Println("ModelMissingError", t)
}

I hope this helps you out.

Solution 3 - Error Handling

Now with Go 1.13 we can use the following from the errors package:

if errors.Is(err, model.ModelMissingError) {...}

See the blog post: https://blog.golang.org/go1.13-errors

Solution 4 - Error Handling

To check the TYPE of the error, use errors.As

> As finds the first error in err's chain that matches target [...] ​An error matches target if the error's concrete value is assignable to the value pointed to by target

Of course type identity is a condition for assignability.

So it would look like:

target := &model.ModelMissingError{} 
if errors.As(err, &target) {
    fmt.Println(target) // no model found for id
}

Pay attention to the two uses of & in the example above. This is because: > As panics if target is not a non-nil pointer to either a type that implements error, or to any interface type.

In your case, you declared Error() string method on the pointer receiver, therefore "a pointer to the type that implements the error interface" to satisfy As is **ModelMissingError. So you need to address twice.


The other method errors.Is checks for value equality.

> An error is considered to match a target if it is equal to that target or if it implements a method Is(error) bool such that Is(target) returns true.

This is useful for example in case of fixed error values, e.g. errors declared as var or const like the standard library io.EOF. As an example:

var ErrModelMissing = errors.New("no model found for id")

func foo() {
    err := bar()
    if errors.Is(err, ErrModelMissing) {
        fmt.Println(err) // no model found for id
    }
}


Consider that the usefulness of Go 1.13 errors.As and errors.Is lies in error unwrapping. If you inspect the error at the top of an arbitrarily long call stack, you must remember that the original error may become wrapped into other errors while being bubbled up. Then directly checking for equality or type assignability is not enough.

	err1 := fmt.Errorf("wrapped: %w", &ModelMissingError{})
	target := &ModelMissingError{}
	fmt.Println(errors.As(err1, &target)) // true
	
	err2 := fmt.Errorf("wrapped: %w", FixedError)
	fmt.Println(errors.Is(err2, FixedError)) // true
	fmt.Println(err2 == FixedError) // false

Additionally, the package github.com/pkg/errors is compatible with errors.As and errors.Is:

// import pkgerr "github.com/pkg/errors"

err3 := pkgerr.Wrap(pkgerr.Wrap(&ModelMissingError{}, "wrapped 1"), "wrapped 2")
fmt.Println(errors.As(err3, &target)) // true

Playground: https://play.golang.org/p/FEzggdBLCqq


Naturally, if you know for sure that the error is not wrapped, a good old type assertion works just as fine:

if myerr, ok := err.(*model.ModelMissingError); ok {
    // handle myerr
}

Solution 5 - Error Handling

If you are Validating across types:

switch err := errorFromFunction(); err.(type) {
	case nil:
		fmt.Println("No Error Function Executed Successfully")
	case *ErrorType1:
		fmt.Println("Type1 Error: ", err)
	case *ErrorType2:
		fmt.Println("Type2 Error", err)
}

If you know the error and want to validate:

err, ok := err.(*ErrorType1)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionmconlinView Question on Stackoverflow
Solution 1 - Error HandlingmconlinView Answer on Stackoverflow
Solution 2 - Error HandlingmiltonbView Answer on Stackoverflow
Solution 3 - Error HandlingMario Pérez AlarcónView Answer on Stackoverflow
Solution 4 - Error HandlingblackgreenView Answer on Stackoverflow
Solution 5 - Error Handlingsuresh PalemoniView Answer on Stackoverflow