My concern with the check
/handle
mechanism is that it makes the error
interface and the zero value special.
This can be avoided at the cost of an increase in implementation complexity and slightly less decrease in boilerplate. Perhaps it's better to avoid the complexity, but this is what it would look like if it were generalized:
handle
requires a type and a conditional. It looks more like an if
statement:
handle err error; err != nil {
// ...
}
This creates multiple handler chains per type. So this would also be valid
handle ok bool; !ok {
// ...
}
as is
handle n int; n < 0 {
// ...
}
check
can only be used if there is handler chain of a matching type in scope, which is easily verifiable at compile time.
At runtime, when a check
is executed, it checks the conditionals of each appropriately-typed handler in scope. If none succeed, the value is discarded and execution carries on as usual. That does mean that is is possible to write code like this:
func f() {
handle ok bool; ok {
fmt.Println("ok!")
}
handle ok bool; !ok {
fmt.Println("!ok")
}
check randomBool()
}
Because this can be used with booleans, it should work with the comma-ok operations: check m[k]
and check <-c
.
This increases the amount written in the common case of err error; err != nil
only slightly and it still needs to be written less often than the current status quo.
Aside from bool
and error
it's unlikely that there will be many legitimate use-cases, but that is no reason to create an overly-specialized mechanism. The generalized mechanism theoretically increases the ability of writing very complicated code with many handlers of different type and condition, but any code written that obliquely would have surely found another means to obfuscate in the absence of this temptation.
A large fraction of the responses suggest named handlers in lieu of (or addition to) a chain. Any thoughts thereon?