Remix.run Logo
kbolino 5 days ago

You do not have to do more work to use errors.Is or errors.As. They work out of the box in most cases just fine. For example:

    package example

    var ErrValue = errors.New("stringly")

    type ErrType struct {
        Code    int
        Message string
    }
    func (e ErrType) Error() string {
        return fmt.Sprintf("%s (%d)", e.Message, e.Code)
    }
You can now use errors.Is with a target of ErrValue and errors.As with a target of *ErrType. No extra methods are needed.

However, you can't compare ErrValue to another errors.New("stringly") by design (under the hood, errors.New returns a pointer, and errors.Is uses simple equality). If you want pure value semantics, use your own type instead.

There are Is and As interfaces that you can implement, but you rarely need to implement them. You can use the type system (subtyping, value vs. pointer method receivers) to control comparability in most cases instead. The only time to break out custom implementations of Is or As is when you want semantic equality to differ from ==, such as making two ErrType values match if just their Code fields match.

The one special case that the average developer should be aware of is unwrapping the cause of custom errors. If you do your own error wrapping (which is itself rarely necessary, thanks to the %w specifier on fmt.Errorf), then you need to provide an Unwrap method (returning either an error or a slice of errors).

simiones 4 days ago | parent | next [-]

Your example is half right, I had misread the documentation of errors.As [0].

errors.As does work as you describe, but errors.Is doesn't: that only compares the error argument for equality, unless it implements Is() itself to do something different. So `var e error ErrType{Code: 1, Message: "Good"} = errors.Is(e, ErrType{})` will return false. But indeed Errors.As will work for this case and allow you to check if an error is an instance of ErrType.

[0] https://play.golang.com/p/qXj3SMiBE2K

kbolino 3 days ago | parent [-]

As I said, errors.Is works with ErrValue and errors.As works with ErrType. I guess the word "Is" is doing too much work here, because I wouldn't expect ErrType{Code:1} and ErrType{Code:0} to match under errors.Is, though ErrType{Code:1} and ErrType{Code:1} would, but yes you could implement an Is method to override that default behavior.

dgunay 4 days ago | parent | prev | next [-]

If you want errors to behave more like value types, you can also implement `Is`. For example, you could have your `ErrType`'s `Is` implementation return true if the other error `As` an `ErrType` also has the same code.

kbolino 3 days ago | parent [-]

If you have a value type, you don't need to implement Is. It's just that errors.New (and fmt.Errorf) doesn't return a value type, so such errors only match under errors.Is if they have the same identity, hence why you typically see them defined with package-level sentinel variables.

kbolino 4 days ago | parent | prev [-]

Probably worth noting that errors.As uses assignability to match errors, while errors.Is is what uses simple equality. Either way, both work well without custom implementations in the usual cases.