Description
I'd like to draft the following proposal for an expression used for error handling. I believe it achieved the following goals :
- It reduces verbosity associated with error handling
- It provides separation between business and error logic without hiding any
- It is intuitive and should be easy to understand even without prior knowledge.
- it clearly shows what is about to happen in any branch.
- Useful in functions that do not return errors - such as http handlers.
For disclosure, while I do think the try proposal needs improvement, it is on the right track as it covers the first two of the aforementioned points. I also don't think this will get any traction, though I do hope for some discussion, to at least know it was considered.
Design
The draft introduces a new syntactic form: an optional or <identifier>: <Statement>
expression added to the Return, Expression, Assignment and ShortValDecl statements.
Edit: after the first feedback, perhaps the grammar should be changed to or <identifier> <block>
to better align with current syntax constructs.
This expression works similarly to try
or check
. If the preceding expression list produces one or more values, the last value is checked whether it is different than its zero value. If it is not, any previous values except it will be returned or assigned. If the last value is nonzero, it will be assigned to the identifier to be used in the scope of the following statement.
The now well known function can be rewritten as:
func CopyFile(src, dst string) error {
r := os.Open(src) or err: return err
defer r.Close()
w := os.Create(dst) or err: return err
defer w.Close()
io.Copy(w, r) or err: return err
w.Close() or err: return err
}
Edit:
func CopyFile(src, dst string) error {
r := os.Open(src) or err {
return err
}
defer r.Close()
...
}
Since the trailing part is a statement in itself, it can be used to decorate errors, handle them (in a code block if needed), breaking and continuing from loops, etc. It also doesn't allow nesting of invocations, which would otherwise reduce readability IMHO.
Drawbacks
One obvious drawback is that it will be more difficult to implement and handle by various tools that a function.
It also may or may not be backwards compatible. Current keywords cannot be used as identifiers,
however this implementation could avoid defining or
as another keyword. I do not have enough knowledge to know this if feasible.
Addendum
While everything is up for debate, I haven't so far restricted the last value to only be of the error type. This construct seems handy when obtaining a value from a map as well - either obtaining it or dealing with its absence.
Edit:
Example:
func A(m map[string]interface{}) {
val := m["key"] or !ok {
log.Println("Key not found")
return
}
data := val.(MyType) or !ok {
log.Println("Key value is not of MyType")
return
}
...
}