Description
$ go version go version devel +e883d000f4 Wed May 29 13:27:00 2019 +0000 linux/amd64
It's typical that Go code has many lines of code that return errors as a consequence of other errors. In large programs, it can be hard to refactor this code with confidence because by changing the code, we might be changing the set of possible errors that can be returned, and some unknown caller might be broken by the refactor because they're relying on a specific error that's no longer returned.
The nature of such breakage is that it often goes unnoticed for long periods because errors are usually infrequent.
For this reason, I believe it's good to maintain what I call error hygiene: to restrict the set of returned errors to those that are explicitly part of a function's contract.
One good example of this is fmt.Errorf("message: %v", err)
which hides the type and value of err from any subsequent Is
or As
call.
Unfortunately, I don't believe it's possible to preserve error hygiene in general with the scheme used for Is and As.
Suppose that we want to define the following function that we'll use to restrict error values to a particular type. This is a bare-minimum requirement for error hygiene.
// KeepType keeps only the given targetType as a possible target
// type for the As function.
//
// For any error e and types T and U:
//
// As(KeepType(e, new(T)), new(U))
//
// will return true if and only if As(e, new(T)) is true and U is
// identical to T. In other words, it hides all types other than T from
// future inspections with As.
func KeepType(err error, targetType interface{}) error
How can we implement this function? For errors that don't fit the designated target type, the implementation is easy: just hide the whole error chain. However, if there's an error that does fit the target type, there's a problem: we can't preserve the target type without also preserving the error chain that's below it, because in general the chain is held in a field within the error value that we might not have access to. We can't make a new error value because then it wouldn't be the required target type.
I think that As
and Is
are thus potentially problematic and we should consider whether it's a good idea to keep them as is. (so to speak :) )