Your comment is a pretty useless blanket statement. At least invite some discussion by coming with a few arguments..
fmt.Print can throw. And HTTP handlers throwing exceptions is silently hidden. If you don't write code assuming anything can throw, then your code is broken.
And don't judge exception by how they are in Java. That's just a clusterfuck. No other language I'm aware of gets exceptions so wrong.
I agree. Well, "fatal" needs to be defined. If an HTTP handler throws an exception, is that fatal for the whole webserver?
Java seems crazy about this. Exceptions seems like it's being treated as just another return value. And that leads to a mess.
But C++? What parts of the C++ standard library have unreasonable exceptions used for errors? (there may be some, I just can't think of any)
And note that you have to throw (no pun intended) away large parts of the language if you remove exceptions. E.g. you can't have constructors without exceptions. How else would you signify "those arguments you gave to the constructor are no bueno".
Go doesn't have constructors, so it's consistent with what it says.
Also see my comment here, about how common or not, discouraged or not, the mere existence of exceptions in a language changes how you must write code to not have it be buggy: https://news.ycombinator.com/item?id=25275580
Yes, in C++ 'goto' is a code smell. It's not in C (greatly used for error handling), but C++ has RAII so `goto` should be rare outside of "clever" code (where "clever" is rarely good).
The mere fact that C++ doesn't have 'finally', and Java does, tells you a lot about how exceptions and RAII differs. If you write a macro for "finally" in C++ then you're doing it wrong.
Pretty much all of my `catch` clauses are in main() (or the root of an event handler), to pretty print the error and/or log to central service, or in a top level event like HTTP handler. `catch` should be about as common in C++ as `rescue` is in Go.
In Java it's fucking everywhere.
Well, your handlers should not generally panic! It's called panic in Go so panicking should be hopefully rare for the peace of mind.
Uhm… no… no they should not.
I feel like you're missing the whole point, here. An interface that is extremely hard to use correctly without turning small bugs into major outages is not a good tool.
And one way to make sure this doesn't happen is to write exception-safe code, because Go has exceptions.
You could also argue that your C++ code shouldn't throw, and I agree. It should very rarely throw. But when it does it should be safe.
If Go had simply not had exceptions then this would have been easier.
> It's called panic in Go so panicking should be hopefully rare for the peace of mind
If you write a web service that hits a bug that panics about once per million requests, and you run 1000 qps, that means your Lock();dothing;Unlock() will deadlock the whole webserver once every 15 minutes.
If you write exception safe code, then it does not.
I don't know of any language that does mudane error handling with exceptions that is not a mess. C++, for instance, is extremely difficult to write exception safe code in.
I don't know about Rust, but Go most certainly did.
> C++, for instance, is extremely difficult to write exception safe code in.
It's WAY easier to write exception safe code in C++ than in Go, because C++ has RAII and scoped defers.
See my example for what a mess Go makes of this in this comment: https://news.ycombinator.com/item?id=25276360
I find C++ exception safe code to be pretty much trivial. Once you get used to "no naked resources" RAII just makes everything exception safe automatically.
However, i'd argue for allowing no unchecked exceptions at all that can be thrown at runtime and instead forcing developers to handle every fail state, that can be encountered in the code that they call.
If a method that you call can fail in 60 different ways, you should at least handle the 20 of them that you understand in their own ways and the rest in a blanket statement. All of that should be checked at compile-time, of course.
Java, .NET and most other ecosystems (both languages and frameworks) don't really seem to want to bother with that, though.
Basically if I define a checked exception function then I am expected to check all exceptions that will be thrown?
Maybe the solution to that is
1. Only allow checked exceptions, and force error handling.
2. Only have 2-3 exception types, not user-extendable: Retryable, Unrecoverable, and Error (for non-user code errors like OOM). OTOH, how to distinguish between different exceptions thrown by the same method, and how to add additional error information (like status_code) would become a problem. Javascript doesn't seem to care tho?
A Result type à la Rust solves most of that: you might still have to wrap lower layers, but at least the noise & syntax is much more sane (never thought I'd say that about Rust!).
Have you investigated Common Lisp, its use of BLOCK/RETURN-FROM and TAGBODY/GO, and how it is possible to close over these lexical constructs to achieve non-local jumps to predefined points on the stack ? All of Common Lisp's error handling system is based on this primitive mechanism (and therefore written in Lisp itself).
(Disclosure: wrote a book on the topic.)
Exceptions are not Structured Programming: they are worse than goto, because at least goto is scope-local. If, maybe, the language enforced that all throwable exceptions are declared by the prototype, then it’d be alright. But I don’t know of any that do. So, given a function call, will it error out? Can you know without inspecting the source, if you even have it?
Thanks, you said it better than me. This is the essence.
Unwind stack, calling cleanup functions in frames that need them, until caught higher up the stacks. Yah, that's exceptions.
The mere fact that they are less used doesn't make them "not exceptions".
And importantly it doesn't matter that they are less common. Merely having exceptions means that everyone has to write exception-safe code.
HTTP handlers silently swallow exceptions, so you can't rely on your program dying if there's a panic.
fmt.Printf can panic as far as you know, too.
Essentially you need RAII, except that because Go doesn't have RAII every single resource needs:
r, err := getResource() if err != nil { … } defer r.Close()
But because "Go doesn't have exceptions" people often don't bother with the defer, and then they get bugs. I see it happening frequently.
You need to write exception-safe code. But also you're not allowed to use exceptions. So it's the worst of both worlds.
I write and review a lot of Go code, and I like the language. But I don't like the dishonesty.
I wasn't aware that this is a real problem in Go land, thanks for the explanation. I wouldn't say the language is "great", was just using it as an example of a modern language where there is a consensus that "they got it right" and not using exceptions, even though error handling is still tedious right now.
I think in Rust you have to consciously fuck up the panic handler to cause similar issues, but I'm not sure.
mu.Lock(); fmt.Print(someoneElsesObject);mu.Unlock();return
You need to get into the habit of writing:
mu.Lock(); defer mu.Unlock(); fmt.Print(someoneElsesObject);return
And this gets extra complicated by the fact that in Go defer runs at end of function, not end of scope. This makes every single for-loop that needs to lock anything hard to read and annoying to write.
for _, a := range stuff { if err:=func()error { mu.Lock();defer mu.Unlock(); return a.stuff()}(); err != nil {return err}}
You can also use defer if you want, but I've never seen it in real code, it's more error-prone and not as flexible. You can build it on top of RAII.
I'm saying "mu.Unlock()" except when deferred, is essentially always a bug. At the very least it's a bug waiting to happen. You need to prove that everything between Lock and Unlock is exception-safe. And that's rarely possible.
As I've said elsewhere you cannot rely on panic triggering program exit, since e.g. HTTP handlers swallow panics.
The fact that you seem to be saying you never see an Unlock deferred proves my point that saying "Go doesn't have exceptions" hurts Go programming. And Go code is in fact full of bugs because there's all this exception-unsafe code.
C++ is naturally exception safe, because RAII. Where it's not exception safe it's because RAII was not used.
Even something as simple as:
mu.Lock()
stats[metricName]++
mu.Unlock()
will throw if there's a path where stats[] map was not inited, and if called in an HTTP handler will leave the lock in place, leading to probably a deadlock of the server. Not great.
let someoneElsesObject = mu.lock();
println!("{}", someoneElsesObject);
return;
when someoneElsesObject goes out of scope, the mutex is unlocked. This happens no matter how it goes out of scope. You cannot forget to do it, because the only way to get access to someoneElsesObject in the first place is locking the mutex, because mutexes wrap the data that they are protecting.https://crates.io/crates/defer exists, but it would be weird to try and use it here, because it can't really be combined directly with this. I guess in theory you could put drop(someoneElsesObject) in the defer block, but like... that already happens for free.
Yeah looks more RAII, and looks like it would not have the problem Go has.
Thanks.
func(){
Code for lock and deferred unlock here
}()
Hence my example where the lambda has to return an error, and the loop has to check for the error. It's A LOT of boilerplate.
Edit: Actually now I don't know what you mean. You clearly replied to my comment that gave a clear example with a return from within the lambda, so what did you think that I didn't know? You took my example and removed extremely commonly needed functionality. So… huh?