The Go language designers explicitly didn't include exceptions because "coupling exceptions to a control structure, as in the try-catch-finally idiom, results in convoluted code. It also tends to encourage programmers to label too many ordinary errors, such as failing to open a file, as exceptional" [1].
I agree with them. While handling errors everywhere is a little painful to write, it enforces better practices by making you acknowledge that things could fail and ignore, handle, or pass the buck.
There are better ways of accomplishing the same thing though, one way is the Either monad in Haskell. Here's an example I post sometimes comparing error handling in Go to Haskell's either monad:
func failureExample()(*http.Response) {
// 1st get, do nothing if success else print exception and exit
response, err := http.Get("http://httpbin.org/status/200")
if err != nil {
fmt.Printf("%s", err)
os.Exit(1)
} else {
defer response.Body.Close()
}
// 2nd get, do nothing if success else print exception and exit
response2, err := http.Get("http://httpbin.org/status/200")
if err != nil {
fmt.Printf("%s", err)
os.Exit(1)
} else {
defer response2.Body.Close()
}
// 3rd get, do nothing if success else print exception and exit
response3, err := http.Get("http://httpbin.org/status/200")
if err != nil {
fmt.Printf("%s", err)
os.Exit(1)
} else {
defer response3.Body.Close()
}
// 4th get, return response if success else print exception and exit
response4, err := http.Get("http://httpbin.org/status/404")
if err != nil {
fmt.Printf("%s", err)
os.Exit(1)
} else {
defer response4.Body.Close()
}
return response4
}
func main() {
fmt.Println("A failure.")
failure := failureExample();
fmt.Println(failure);
}
The equivalent Haskell code: failureExample :: IO (Either SomeException (Response LBS.ByteString))
failureExample = try $ do
get "http://www.httpbin.org/status/200"
get "http://www.httpbin.org/status/200"
get "http://www.httpbin.org/status/200"
get "http://www.httpbin.org/status/404"
main = failureExample >>= \case
Right r -> putStrLn $ "The successful pages status was (spoiler: it's 200!): " ++ show (r ^. responseStatus)
Left e -> putStrLn ("error: " ++ show e)What often starts out as simply a return, by the time something gets to production quality, has often changed.
If you're using Go for its core use case, network servers, the error handling turns out to be very solid, precisely because almost every other error handling paradigm strongly encourages you to just lump all the errors together and not think about them individually. (Yes, that includes Option<>.) I can see where that might be very annoying on a desktop GUI app or something, but if you are not thinking about every single error in your at-scale network server, you're actually doing it wrong.
Why? Seems to me that it's precisely equivalent to Golang's error handling story (assuming you mean something like Result). In fact, it's a lot easier to handle errors individually that way, both because it tends to make it harder to use a return value without checking for errors and because its generic nature means that you can handle different errors differently without having to deal with interfaces.
If you mean that it's more typing to write "return err" and it forces you to think about it more, I don't really buy that. "return err" is 10 characters; "map" (e.g. in Haskell) is 3 and "try!()" is 6. I really doubt that the difference between 3 and 10 characters has any practical outcome. For Golang programmers, "return err" is muscle memory.
"For Golang programmers, "return err" is muscle memory."
First, as I said, no, I actually think about it every time. And second, I bet you end up with "muscle memory" default handling under any scheme (for instance, exceptions: don't catch them or rethrow them)... in the end, you can bring the horse to water but you can't make it drink. You can only feel good that at least you brought it and you did your part. Option<>, even manually unwrapped, does not force the programmer to do something sensible with the error any more than any thing else does, or indeed, can.
It feels like it would be a lot cleaner to add syntax support for exceptions and call it a day.