go: proposal: errors: expose Iser and Aser

The interfaces for errors.Is and errors.As seem stable and complete. Unfortunately, it requires reading through the godoc and use of a custom type to ensure that your custom error type implements interface{ As(interface{}) bool } or interface{ Is(error) bool }.

I propose exposing the following symbols:

package errors

// Aser is the interface implemented by types that can be converted into other types.
//
// See As for full usage.
type Aser interface{ As(interface{}) bool }

// Iser is the interface implemented by types that are comparable to other errors.
//
// See Is for full usage.
type Iser interface{ Is(error) bool }

And fixing up errors.Is and errors.As to document and use them:

--- a/src/errors/wrap.go
+++ b/src/errors/wrap.go
@@ -27,7 +27,7 @@
 // repeatedly calling Unwrap.
 //
 // 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.
+// it implements Iser such that Is(target) returns true.
 //
 // An error type might provide an Is method so it can be treated as equivalent
 // to an existing error. For example, if MyError defines
@@ -46,7 +46,7 @@
 		if isComparable && err == target {
 			return true
 		}
-		if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
+		if x, ok := err.(Iser); ok && x.Is(target) {
 			return true
 		}
 		// TODO: consider supporting target.Is(err). This would allow
@@ -65,9 +65,9 @@
 // repeatedly calling Unwrap.
 //
 // An error matches target if the error's concrete value is assignable to the value
-// pointed to by target, or if the error has a method As(interface{}) bool such that
-// As(target) returns true. In the latter case, the As method is responsible for
-// setting target.
+// pointed to by target, or if the error implements Aser such that As(target)
+// returns true. In the latter case, the As method is responsible for setting
+// target.
 //
 // An error type might provide an As method so it can be treated as if it were a
 // a different error type.
@@ -92,7 +92,7 @@
 			val.Elem().Set(reflectlite.ValueOf(err))
 			return true
 		}
-		if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) {
+		if x, ok := err.(Aser); ok && x.As(target) {
 			return true
 		}
 		err = Unwrap(err)

This allows error implementers to ensure that the interfaces are implemented correctly, while also cleaning up errors documentation and internal logic:

var _, _, _ = errors.Iser(myError{}), errors.Aser(myError{}), error(myError{})

That being said, the names Iser and Aser sound like fantasy characters and are not strictly required, since they do not exist today and errors.Is and errors.As work fine. Furthermore, I cannot think of a reason to pass, accept, or typecast these interfaces, so their usefulness is limited. That being said, json.Marshaler suffers from similar issues, yet it exists.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 5
  • Comments: 19 (13 by maintainers)

Most upvoted comments

Simply implementing the interfaces seems like not enough if you are checking correctness. If you write a test, you’ll check the whole end-to-end implementation, including the method signatures.

One thing we could do is update the cmd/vet check to check the signature of an Is or As method on a type implementing error, like we do in the ‘stdmethods’ check for things like WriteByte.

Both of those would seem better than exporting Iser/Aser.

@smasher164 Thanks, but I don’t think it’s ideal to reopen a closed proposal. The conversation gets complicated and it’s harder to see what the decision was and why.

I think it’s preferable to open a new proposal, cite this one, and say “we should revisit this because of this new information that was not considered in the original discussion.” Thanks.

Can this be exported for documentation purposes. Like how json.Unmarshaler is exported even though almost no one will use it directly. Hunting for the method signature in the errors.Is doc comments isn’t very nice and having it exported will make it easier to point people to

Any package could define those interfaces itself, of course. I’m not sure how compelling it is to define them in the standard library. I would not expect many packages to use them.

Reopening to merge discussion on #45568.

@carnott-snap I think a different proposal would be better. Thanks.

While I agree that any package can define these symbols, exporting them from errors makes for a formal contract that is verifiable at compile time. For me, this is one of the main benefits of using a compiled language.