return value of the function call `os.Open`. The variable could have any valid
variable name and is defined only in the `else` block.
I like to read the example above with an implicit try before the function call.
I.e., try opening filename.ext, else if err is not nil... For this reason, I
call this construct else catch.
A longer example from Error Handling - Problem Overview:
func CopyFile(src, dst string) error {
r, err := os.Open(src)
if err != nil {
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
defer r.Close()
w, err := os.Create(dst)
if err != nil {
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
if _, err := io.Copy(w, r); err != nil {
w.Close()
os.Remove(dst)
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
if err := w.Close(); err != nil {
os.Remove(dst)
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
}
would become:
func CopyFile(src, dst string) error {
r := os.Open(src)
else err != nil {
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
defer r.Close()
w := os.Create(dst)
else err != nil {
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
io.Copy(w, r)
else err != nil {
w.Close()
os.Remove(dst)
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
w.Close()
else err != nil {
os.Remove(dst)
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
}
By using the else catch we declutter the function calls, more specifically
assignments, thus improving readability. Error handling stays familiar,
explicit, and simple.
And since the variable in else catch is defined only in the `else` block, we do
not need to re-assign the `err` variable again and again, making the code more
obvious and unambiguous.
I would like to note that the else catch example is longer, but I think length
should not be equated with readability.
Else catch is not meant to replace the `if err != nil` idiom, but rather
complement it. It would allow for a clear separation of return values we "care"
about and error handling. Or put another way, it would allow for a clear
separation of a happy and an unhappy path of execution.
What about the error handling blocks?
We should not try to stash away error handling. Most gophers are already used to
skipping error handling blocks while reading Go. Using else catch would make
that even easier. IDEs could collapse else catch blocks on command.
Else catch is orthogonal and can be used for anything, not just error handling.
We just have to remember that it always takes the last (and no more) return
value of a function call from the previous line.
For example, we can test for existence of a key:
m := map[string]int{
"route": 66,
}
i := m["route"]
else !ok {
i = -1
}
Now, there is a slight problem with current else catch. If it is basically an
`if`, what happens if we use multiple variables in the condition? Which variable
came from the last return value?
firstTimeBonus := true
apples := map[string]int{
"bob": 7,
...
}
apples["john"]
else !ok && firstTimeBonus {
apples["john"] = 3
}
In this case we can probably figure out that variable `ok` is the last return
value of `apples["john"]`. But it is not explicitly stated that `ok` came from
the last return value. What if `ok` variable was already declared and we
unknowingly shadowed it? And how would the compiler know to which variable to
assign the last return value?
This could be solved by allowing only one variable in the `else` condition.
Then the example above would be invalid, which it should be, because the if
statement would be more appropriate. Also if there was only one variable allowed
in the condition, there would be no confusion as to where the variable came
from.
I believe something like else catch could be beneficial to Go, mainly improving
readability. But there are a few problems to consider.
`else` would have different semantics depending on the context. This should not
be a problem for people, but it could be for the compiler and other tools.
Go would have a new, not widely known construct. How much would it impact the
learning curve of the language?
This is just an idea and I am interested in what you think.
- Would else catch be useful to you?
- Could it be simpler? Maybe a keyword or a built-in that could take the last return value and we could use if with a short statement instead of `else`.
- Do you see a problem with it? Does it feel too much like magic?
- Is it trying to solve a problem you even have?
- Do the pros outweigh the cons?