7 ms·
There is a lot that I like about Go. Error handling is not one of them. On one hand, I appreciate the simplicity of it all. Nothing special about an error, it’s
by eddythompson80 3y ago
There is a lot that I like about Go. Error handling is not one of them. On one hand, I appreciate the simplicity of it all. Nothing special about an error, it’s just part of how you do everything else. But on the other hand, there is something clearly special about an error. It’s something 100% of go users have to deal with in almost every single function call. There is something clearly special about it. These grassroots patterns and efforts to handle multiple errors per function, or async errors etc all should really have better solutions in the language.
I understand that maybe the language authors in the early days didn’t want to lock anyone into a strict paradigm for how to deal with errors. Like I’m not thrilled about Java’s approach either, but that can never change. But Go is a very popular and established language now. It’s time to fix the error handling mess. There are so many good examples out there to get inspiration from. F#, Swift and Rust have a perfect error handling mechanism.
- lolinder 3y ago> F#, Swift and Rust have a perfect error handling mechanism. Rust's is good but not perfect. I often find myself missing stack traces (there are solutions but they're not easy to use), and you're still constrained to a single type of error per function, which means you see a proliferation of specialized error types that are mutually incompatible and have to be converted back and forth.
- eddythompson80 3y agoInteresting. That’s fair. To be completely honest, out of the 3 I listed, I’m only familiar with putting an F# service into production. We’re a very heavy C# shop (legacy Java shop, but anything since 2015 has been in C#) and I thought I could slip in an F# project in there. And I was able to until I needed to hand it over to another team that wrote wrappers to all the F# code in C# and did all new development in C#. Swift and Rust error handling reminded me of how nice error handing felt in F#. But I only did very simple toy projects in them.
- treyd 3y ago> you're still constrained to a single type of error per function This is true, but the ? operator expands into a form that does `.into()` conversions of the error variants. If there's a implementation of `From`/`Into` between the error type you're unwrapping and the error type on the function, it automatically converts. This is aided by the "thiserror" crate which provides a derive macro that can generate these automatically.
- dlisboa 3y agoOne thing I don't get (and would honestly appreciate if was explained to me) is how the Result monad differs significantly from Go's error handling, other than being a "true" monad. Most Rust code I see does things like (from the docs): let greeting_file_result = File::open("hello.txt"); let greeting_file = match greeting_file_result { Ok(file) => file, Err(error) => // handle err }; It isn't much different from: file, err := os.Open("hello.txt") if err != nil { // handle error } I've come to appreciate Go's simple solution as you get pretty much 90% of what you want from an operation that might produce an error (either the value or an error), and the flow control aspect of it is more explicit than with exceptions. Maybe I don't get Monads, but it seems pretty much equivalent for the common use case.
- lolinder 3y agoThe killer feature of Rust's Result isn't actually the monad itself, it's the ? operator. Being able to concisely say "if there's an error, bail out by returning it" gets you pretty close to exception-level convenience with just a bit more explicit syntax showing where an error might come from.
- preseinger 3y agoyou don't want exception-style "convenience", that's the whole point you want to be able to read code and see a single control flow ? subverts that core requirement
- nulld3v 3y agoThe problem with exceptions is that they can come from any line of code and cause a "return". Rust's question mark solves the issue because it marks which lines of code can cause a "return". Therefore, you can always see see the control flow of a function. Moreover, you can go even further if you really really really want a single control flow. You can write a clippy lint to disallow early returns (ban "return" keyword) and question marks. That said, I really don't think this is a good idea. The "return" keyword exists for a good reason.
- randomdata 3y ago> It’s something 100% of go users have to deal with in almost every single function call. There is something clearly special about it. I can't help but feel there is an even greater generalization here. The error problem you speak of actually applies to every type. To zoom in on errors alone may be missing the forest for the trees. It seems what is special is the need to handle values returned by a function, which includes, but is not limited to, error values. It is something 100% of Go users will have to do almost every time they call a function. Even those which do not return errors. > Swift and Rust have a perfect error handling mechanism. Within their respective languages they may be a good fit, but those languages are producer centric. Go is consumer centric. That leaves an impedance mismatch. I do think there is something better out there for Go, but I'm not sure that is where we are going to find it.
- preseinger 3y agoif you write a line of code that can fail, then you should deal with the possibility of that line failing there, directly, in-line _how_ you deal with that failure is a separate question but it's critical that every fallible expression explicitly and visibly demonstrates the possibility of failure this is in no way an "error handling mess" -- on the contrary, it is basically the only way to produce robust and reliable software at scale
- lanstin 3y agoi wish people understood this thinking about and handling all errors, as a prime functiom of the code, is why things like Linux and C Python and X server and git and so on are so reliable.
- kaba0 3y agoThe only reasons those are reliable is that they have millions of hours of runtime, so that the easier bugs have all been fixed now. If anything, they have become successful in spite of C.
- lanstin 3y agothey are successful in spite of the memory unsafety of C because of the culture that the behavior of a failing system is a principle feature of the code, which is common thinking in C and easier to do when you don't have the crutch of exceptions.
- cwalv 3y agoIn my experience, the vast majority of the time the best (or only) way to correctly deal with the error is to propagate it up the stack. Making this optional (having to explicitly check return values) doesn't make it more likely to be done.
- preseinger 3y agoyou say "optional", i say "explicit" it's important that i see the `return` keyword in the source code