Some exciting news came out of GopherCon 2018 this week, Go 2 Draft Designs! These draft designs are the first steps in defining how these new features might work in the future version of GoLang. The main purpose is to generate discussion within the community so the best possible solution to the problem is implemented.
Here are the 3 drafted features:
- Error Handling
- Error Values
- Generics!
Error Handling
In Go 2, the Go team wants to the way gophers write code to handle errors, specifically making it less verbose. They’ve introduced the Go 2 Error Handling — Problem Overview and Draft Design to provide some details and thoughts on the subject.
One of my personal pain points with GoLang is the amount of code you have to write to properly handle errors. On one hand, I appreciate that Go has forced me to think about error handling all the time, and it’s actually improved my overall error handling across all languages. On the other hand, it’s a pain to have to provide so much additional code for error handling!
Currently error handle and checks lead to a low SNR making it hard to quickly digest what this code is doing. Here’s an example from the draft spec that should feel familiar for gophers.
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)
}
}
Here’s the idea the draft spec is throwing around to solve this problem using checks
and handlers
:
func CopyFile(src, dst string) error {
handle err {
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
r := check os.Open(src)
defer r.Close()
w := check os.Create(dst)
handle err {
w.Close()
os.Remove(dst) // (only if a check fails)
}
check io.Copy(w, r)
check w.Close()
return nil
}
Both error checks and error handling must remain explicit, meaning visible in the program text.
Checking for errors, vs handling errors. Checing for errors just checks for the err condition and returns an error. Error handling is more about how you deal with the errors that arise. For me, I mostly add a bunch of context to where the error occured and any relevant data that might be useful for trying to prevent those errors when I’m fixing the bug.
We do not want to repeat the pitfalls of exception handling.
What they don’t want to do is implement exception handling (try/catch) like we’re used to in other languages. They want to keep error handling explicit (just less explicit than it is now) so that we have to be good gophers and deal with all errors that pop up.
The handle statement defines a block, called a handler, to handle an error detected by a check. A return statement in a handler causes the enclosing function to return immediately with the given return values. A return without values is only allowed if the enclosing function has no results or uses named results. In the latter case, the function returns with the current values of those results.
A handler chain function takes an argument of type error and has the same result signature as the function for which it is defined. It executes all handlers in lexical scope in reverse order of declaration until one of them executes a return statement. The identifier used in each handle statement maps to the argument of the handler chain function.
It is a compile-time error for a handler chain function body to be empty: there must be at least one handler, which may be a default handler.