I wanted to hear the community’s thoughts on using generics to do things like testing an error by its type rather than its value. The following snippet is part of a code that I’ve been writing to get a feel of how generics works in Go as of 1.18:
package handy
import "errors"
// As is an inline form of errors.As.
func As[Error error](err error) (Error, bool) {
var as Error
ok := errors.As(err, &as)
return as, ok
}
// IsType reports whether or not the type of any error in err's chain matches
// the Error type.
func IsType[Error error](err error) bool {
_, ok := As[Error](err)
return ok
}
To be completely honest this feels very gimmicky and un-Go like to me. It is legal Go code but it feels like an abuse of generics. However at the same time, it is very useful in situations where the type of the error matters rather than the value (eg. checking for ent.ConstraintError
type to catch and handle constraint errors). While ent.ConstraintError
can be caught using ent.IsConstraintError(err)
, in my case I wanted to separate the ORM layer from the rest of my application so I wrapped the error in a custom type, but writing a myapp.IsXxxError
was cumbersome. My instinct pre-generics would have been “just write the myapp.IsXxxError
function”, but with generics I started to wander around to see if at least I can do it.
I’ve also come up with some hacks like creating value-initialized pointers but I feel the same about this too:
package handy
// New returns a pointer initialized with the given value.
func New[T any](v T) *T {
return &v
}
It sure is handy to use but I’m having a sort of identity crisis where it feels like the code I’m writing is deviating away from the design principles of Go. I’d love to hear your feedback.