5 ms·
In practice, I have found the benefit of explicit typing to outweigh the downsides of needing to declare the types. As a concrete example, it means you can tar
by atombender 1y ago
In practice, I have found the benefit of explicit typing to outweigh the downsides of needing to declare the types.
As a concrete example, it means you can target types with precision in the API layer:
switch e := err.(type) {
case UserNotFound:
writeJSONResponse(w, 404, "User not found")
case interface { Timeout() bool }:
if e.Timeout() {
writeJSONResponse(w, 503, "Timeout")
}
}
I skimmed the article and didn't see the author proposing a way to do that with their arbitrary key/value map.
Of course, you could use something else like error codes to translate groups of errors. But then why not just use types?
But as I suggested in my other comment, you could also generalize it. For example:
return meta.Wrap(err, "storing user", "userID", userID)
Here, Wrap() is something like:
func Wrap(err error, msg string, kvs ...any) {
return &KV{
KV: kvs,
cause: err,
msg: msg,
}
}
This is the inverse of the context solution. The point is to provide data at the point of error, not at every call site.
You can always merge these later into a single map and pay the allocation cost there:
var fields map[string]any
for err != nil {
if e, ok := err.(*KV); ok {
for i := 0; i < len(KV.KV); i += 2 {
fields[e.KV[i].(string)] = e.KV[i+1]
}
}
err = errors.Unwrap(err)
}
- radlad 1y agoYou can also just assign your errors to variables, and typically you only need to do so for the innermost error. Wrapping calls can just use Errorf with the %w operator. Horses for courses.
- kiitos 1y agoerr.(type) fails when errors are wrapped, which is common and needs to be accommodated. To do what you're trying to do, you need to use errors.As.
- atombender 1y agoSure. It was not intended to be a complete example.
- kiitos 1y agoIt's not that the example is incomplete, it's that it's incorrect! :)
- atombender 1y agoNo, it's just incomplete. The same code just needs to unwrap: for err != nil { switch e := err.(type) { case UserNotFound: writeJSONResponse(w, 404, "User not found") return case interface { Timeout() bool }: if e.Timeout() { writeJSONResponse(w, 503, "Timeout") return } } err = errors.Unwrap(err) }
- kiitos 1y ago`err.(type)` is incorrect, at least in general. Calling `errors.Unwrap` in application code like this is almost always a red flag indicating a design error. And in this case it definitely is!
- atombender 1y agoThat is literally what errors.As() does, finding a value up the cause chain that can be coerced to the target type.
- kiitos 1y agoIf you're re-implementing errors.As's unwrapping behavior in your application code in order to parse/evaluate errors, that's a mistake and a design error. You'd never call Unwrap outside of a custom error type's method set, and even then you'd never use a `for` loop like you're doing in your example. > it means you can target types with precision in the API layer The only situation where you need to get precise error types is when you need to provide specific details from those specific types to the consumer, which is rare. And even in those rare cases, user code does that work via errors.As, not this manual Unwrap loop process you're suggesting here.