Skip to content

proposal: errors: expose Iser and Aser #39539

Closed
@carnott-snap

Description

@carnott-snap

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions