82

I have an error value which when printed on console gives me Token is expired

How can I compare it with a specific error value? I tried this but it did not work:

if err == errors.New("Token is expired") {
      log.Printf("Unauthorised: %s\n", err)
}
Carson
  • 6,105
  • 2
  • 37
  • 45
codec
  • 7,978
  • 26
  • 71
  • 127
  • 6
    I would avoid using accepted approach. Take a look on Dαve Cheney presentation about error handling https://www.youtube.com/watch?v=lsBF58Q-DnY. I will definitely answer your question. – s7anley Aug 24 '16 at 13:07

11 Answers11

97

Declaring an error, and comparing it with '==' (as in err == myPkg.ErrTokenExpired) is no longer the best practice with Go 1.13 (Q3 2019)

The release notes mentions:

Go 1.13 contains support for error wrapping, as first proposed in the Error Values proposal and discussed on the associated issue.

An error e can wrap another error w by providing an Unwrap method that returns w.
Both e and w are available to programs, allowing e to provide additional context to w or to reinterpret it while still allowing programs to make decisions based on w.

To support wrapping, fmt.Errorf now has a %w verb for creating wrapped errors, and three new functions in the errors package ( errors.Unwrap, errors.Is and errors.As) simplify unwrapping and inspecting wrapped errors.

So the Error Value FAQ explains:

You need to be prepared that errors you get may be wrapped.

If you currently compare errors using ==, use errors.Is instead.
Example:

if err == io.ErrUnexpectedEOF

becomes

if errors.Is(err, io.ErrUnexpectedEOF)
  • Checks of the form if err != nil need not be changed.
  • Comparisons to io.EOF need not be changed, because io.EOF should never be wrapped.

If you check for an error type using a type assertion or type switch, use errors.As instead. Example:

if e, ok := err.(*os.PathError); ok

becomes

var e *os.PathError
if errors.As(err, &e)

Also use this pattern to check whether an error implements an interface. (This is one of those rare cases when a pointer to an interface is appropriate.)

Rewrite a type switch as a sequence of if-elses.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
52

This answer is for Go 1.12 and earlier releases.

Define an error value in a library

package fruits

var NoMorePumpkins = errors.New("No more pumpkins")

Do not create errors with errors.New anywhere in the code but return the predefined value whenever error occurs and then you can do the following:

package shop

if err == fruits.NoMorePumpkins {
     ...
}

See io package errors for reference.

This can be improved by adding methods to hide the check implementation and make the client code more immune to changes in fruits package.

package fruits

func IsNoMorePumpkins(err error) bool {
    return err == NoMorePumpkins
} 

See os package errors for reference.

Grzegorz Żur
  • 47,257
  • 14
  • 109
  • 105
  • One downside to this is that another package could potentially overwrite the value of the exported `NoMorePumpkins` variable. Would be nice if there were a way to do that with a `const` – captncraig May 22 '17 at 13:24
  • 1
    @captncraig well then you could use a getter function – wingerse May 31 '17 at 20:59
  • @WingerSendon good point. That may be my favorite solution yet. – captncraig May 31 '17 at 21:01
  • but why would you overwrite an other's package error? – math2001 Sep 27 '18 at 01:48
  • 4
    The practical problem is not someone may overwrite `fruits.NoMorePumpkins`, it's that they may *wrap* it, and the `==` will fail. – cbednarski Apr 19 '19 at 06:32
  • 1
    @cbednarski check out Go 1.13's new error built-ins. There is a new way to make hierarchical errors so you can check whether or not it was wrapped. – d1str0 Jan 30 '20 at 05:14
34

Try

err.Error() == "Token is expired"

Or create your own error by implementing the error interface.

Sridhar
  • 2,416
  • 1
  • 26
  • 35
  • 39
    This is terrible advice. What if the error text changes? You should compare it to an error `var`. –  Jun 21 '19 at 10:19
  • 1
    Error text is typically implementation-specific and isn't part a package's contract, therefore this is not advisable. Although this is safe if done internally (once you ensure the error strings don't change), I would stick with using custom error types for consistency and readability. – David Callanan Apr 21 '20 at 15:49
  • 12
    I don't know why my comment keeps getting deleted! This answer is not advice so can't be `terrible advice`. The question itself has a comparison of string literals. I attempted to answer the question, not provide advice on best practice. – Sridhar Jan 07 '21 at 13:00
20

It's idiomatic for packages to export error variables that they use so others can compare against them.

E.g. If an error would came from a package named myPkg and was defined as:

var ErrTokenExpired error = errors.New("Token is expired")

You could compare the errors directly as:

if err == myPkg.ErrTokenExpired {
    log.Printf("Unauthorised: %s\n", err)
}

If the errors come from a third party package and that doesn't use exported error variables then what you can do is simply to compare against the string you get from err.Error() but be careful with this approach as changing an Error string might not be released in a major version and would break your business logic.

jussius
  • 3,114
  • 15
  • 21
  • 2
    A lot better than comparing strings. – luben May 23 '17 at 21:17
  • 1
    You shouldn't really test for third-party errors as those errors are implementation-details. For example, if you had some database package and a local implementation used a map, there might be a key error if an item didn't exist (or in the case of Go, ok would be false). If it was instead using a SQL backend, you might instead get a row not found error. In my opinion, packages should never propagate errors directly. Either a) map them to more appropriate custom error type (such as item not found error for the previous example), or b) wrap it with custom "implementation error" type, or c) panic – David Callanan Apr 21 '20 at 19:17
17

The error type is an interface type. An error variable represents any value that can describe itself as a string. Here is the interface's declaration:

type error interface {
    Error() string
}

The most commonly-used error implementation is the errors package's unexported errorString type:

// errorString is a trivial implementation of error.
type errorString struct {
    s string
}

func (e *errorString) Error() string {
    return e.s
}

See this working code output (The Go Playground):

package main

import (
    "errors"
    "fmt"
    "io"
)

func main() {
    err1 := fmt.Errorf("Error")
    err2 := errors.New("Error")
    err3 := io.EOF

    fmt.Println(err1)         //Error
    fmt.Printf("%#v\n", err1) // &errors.errorString{s:"Error"}
    fmt.Printf("%#v\n", err2) // &errors.errorString{s:"Error"}
    fmt.Printf("%#v\n", err3) // &errors.errorString{s:"EOF"}
}

output:

Error
&errors.errorString{s:"Error"}
&errors.errorString{s:"Error"}
&errors.errorString{s:"EOF"}

Also see: Comparison operators

Comparison operators compare two operands and yield an untyped boolean value. In any comparison, the first operand must be assignable to the type of the second operand, or vice versa.

The equality operators == and != apply to operands that are comparable.

Pointer values are comparable. Two pointer values are equal if they point to the same variable or if both have value nil. Pointers to distinct zero-size variables may or may not be equal.

Interface values are comparable. Two interface values are equal if they have identical dynamic types and equal dynamic values or if both have value nil.

A value x of non-interface type X and a value t of interface type T are comparable when values of type X are comparable and X implements T. They are equal if t's dynamic type is identical to X and t's dynamic value is equal to x.

Struct values are comparable if all their fields are comparable. Two struct values are equal if their corresponding non-blank fields are equal.


So:

1- You may use Error(), like this working code (The Go Playground):

package main

import (
    "errors"
    "fmt"
)

func main() {
    err1 := errors.New("Token is expired")
    err2 := errors.New("Token is expired")
    if err1.Error() == err2.Error() {
        fmt.Println(err1.Error() == err2.Error()) // true
    }
}

output:

true

2- Also you may compare it with nil, like this working code (The Go Playground):

package main

import (
    "errors"
    "fmt"
)

func main() {
    err1 := errors.New("Token is expired")
    err2 := errors.New("Token is expired")
    if err1 != nil {
        fmt.Println(err1 == err2) // false
    }
}

output:

false

3- Also you may compare it with exact same error, like this working code
(The Go Playground):

package main

import (
    "fmt"
    "io"
)

func main() {
    err1 := io.EOF
    if err1 == io.EOF {
        fmt.Println("err1 is : ", err1)
    }
}

output:

err1 is :  EOF

ref: https://blog.golang.org/error-handling-and-go

14

It's being discouraged to compare errors by strings. Instead you should compare errors by value.

package main

import "errors"

var NotFound = errors.New("not found")

func main() {
    if err := doSomething(); errors.Is(err, NotFound) {
        println(err)
    }
}

func doSomething() error {
    return NotFound
}

It is especially useful if you are library author and would like to export errors so users can act differently on different type of errors. Standard library does it as well.

Problem with this approach is that exported values can be changed by anyone as Go doesn't support immutable values. Nothing prevents you, though, to use string as an error and make it const.

package main

type CustomError string

func (ce CustomError) Error() string {
    return string(ce)
}

const NotFound CustomError = "not found"

func main() {
    if err := doSomething(); errors.Is(err, NotFound) {
        println(err)
    }
}

func doSomething() error {
    return NotFound
}

It is more verbose but safer approach.

wst
  • 4,040
  • 4
  • 41
  • 59
5

You should first consider comparing errors by value, as described in other solutions with:

if errors.Is(err1, err2) {
  // do sth
}

However in some cases the error returned from a function is a bit complex, e.g. an error is being wrapped multiple times, with a context being added to it in each function call like fmt.Errorf("some context: %w", err), and you may simply just want to compare the error message of two errors. In such cases you can do this:

// SameErrorMessage checks whether two errors have the same messages.
func SameErrorMessage(err, target error) bool {
    if target == nil || err == nil {
        return err == target
    }
    return err.Error() == target.Error()
}

func main() {
  ...
  if SameErrorMessage(err1, err2) {
     // do sth
  }

}

Note that if you simply use

if err1.Error() == err2.Error() {
  // do sth
}

You might face nil pointer dereference runtime error if either of err1 or err2 be nil.

3

To add to @wst 's answer, in some cases, the errors.Is(err, NotFound) approach may not work for reasons I am trying to figure out too. If someone knows, please let me know in the comments.

enter image description here

But I found a better approach to use it in the following way which was working for me:

if NotFound.Is(err) {
    // do something
}

Where var NotFound = errors.New("not found") is an exported common error declared.

In my case, the solution was

if models.GetUnAuthenticatedError().Is(err) {
    // Do something
}
Ayush Kumar
  • 833
  • 2
  • 13
  • 30
0

I want to post one case where errors.Is could work well for custom errors with non-comparable values.

type CustomError struct {
    Meta    map[string]interface{}
    Message string
}

func (c CustomError) Error() string {
    return c.Message
}

var (
    ErrorA = CustomError{Message: "msg", Meta: map[string]interface{}{"key": "value"}}
)

func DoSomething() error {
    return ErrorA
}

func main() {
    err := DoSomething()
    if errors.Is(err, ErrorA) {
        fmt.Println("error is errorA")
    } else {
        fmt.Println("error is NOT errorA")
    }
}

Output

error is NOT errorA

Playground


Root Cause

The reason is errors.Is checks whether the target is comparable or not

func Is(err, target error) bool {
    if target == nil {
        return err == target
    }

    isComparable := reflectlite.TypeOf(target).Comparable()

The comparable type in Go are

booleans, numbers, strings, pointers, channels, arrays of comparable types, structs whose fields are all comparable types

Since the Meta map[string]interface{} of CustomError is NOT comparable, errors.Is checks failed.


Workaround

  • One workaround is declare the ErrorA = &CustomError{Message: "msg", Meta: map[string]interface{}{"key": "value"}} as pointer.
  • You could implement a CustomError.Is(target error) to compare this custom type. Per @kaizenCoder comment.
func (c CustomError) Is(err error) bool {
    cErr, ok := err.(CustomError)
    if !ok {
        return false
    }

    if c.Message != cErr.Message || fmt.Sprint(c.Meta) != fmt.Sprint(c.Meta) {
        return false
    }

    return true
}

    err := DoSomething()
    if ErrorA.Is(err) {
        fmt.Println("error is errorA")
    } else {
        fmt.Println("error is NOT errorA")
    }

Playground

zangw
  • 43,869
  • 19
  • 177
  • 214
0

The errors.Is only works for constant errors. If errors are created dynamically, e.g. for including context information, the new error instance cannot be compared with this. In this case error.As must be used.

k_o_
  • 5,143
  • 1
  • 34
  • 43
0

None of the answers were satisfactory, so here's mine:

Comparing Constant Errors

If your error is a simple string, pull the error our of your method and save it as a variable using errors.New, then compare using errors.Is. Example:

import (
    "errors"
    "fmt"
)

var errSomethingWrong = errors.New("something is wrong")

func main() {
    var err error
    err = errSomethingWrong
    fmt.Println("is errSomethingWrong:", errors.Is(err, errSomethingWrong))
}

Output:

is errSomethingWrong: true

Playground

Comparing Wrapped Errors

The same function works even for wrapped errors! Use fmt.Errorf(" ... %w ... ", err) to wrap the error in a way it can be compared again later. errors.Is will return true even if the error you're comparing against is wrapped this way. Example:


import (
    "errors"
    "fmt"
)

var (
    myWrappedError      = errors.New("something went wrong")
    myOtherWrappedError = errors.New("this and that")
)

func main() {
    var err error
    err = fmt.Errorf("%w: %w", myWrappedError, myOtherWrappedError)

    fmt.Println("error:", err)
    fmt.Println("is myWrappedError:", errors.Is(err, myWrappedError))
    fmt.Println("is myOtherWrappedError:", errors.Is(err, myOtherWrappedError))
}

Output:

error: something went wrong: this and that
is myWrappedError: true
is myOtherWrappedError: true

Playground

Nearoo
  • 4,454
  • 3
  • 28
  • 39