5 ms·
And the other one is the tendency for Go's design to say "exceptions are allowed for me but not for thee". When I worked at Google I used other languages by so
by btilly 7y ago
And the other one is the tendency for Go's design to say "exceptions are allowed for me but not for thee".
When I worked at Google I used other languages by some of the same authors and they showed the same design philosophy. Make things with an enforced simplicity, and where there were more special use cases that the language designer needed, they created escape hatches for themselves but not the language users.
- Animats 7y agoAnd the other one is the tendency for Go's design to say "exceptions are allowed for me but not for thee". Yes. Exceptions are kind of a pain, but the workarounds for not having them are worse. Passing back "result" types tends to lose the details of the problem before they are handled. Rust is on, what, their third error handling framework? Exceptions have a bad reputation because C++ and Java botched them. You need an exception hierarchy, where you can catch exception types near the tree root and get all the children of that exception type. Otherwise, knowing exactly what exceptions can be raised in the stack becomes a huge headache. Python comes close to getting this right. Incidentally, the "with" clause in Python is one of the few constructs which can unwind a nested exception properly. Resource Acquisition Is Initialization is fine; it's Resource Deletion Is Cleanup that has problems. Raising an exception in a destructor is not happy-making. It's easier in garbage-collected languages, which, of course, Go is. You have to be more careful about unwinding in non garbage collected languages, which was the usual problem in C++.
- AnimalMuppet 7y ago> Exceptions have a bad reputation because C++ and Java botched them. Okay... > You need an exception hierarchy, where you can catch exception types near the tree root and get all the children of that exception type. Didn't Java do exactly that?
- Animats 7y agoThey tried, but the hierarchy is not well-designed. You want a clear distinction between "program has an internal problem" and "external thing (network, file, database, remote service, etc.) had a problem". You usually want to catch "external thing had a problem" in whatever wanted to talk to the external thing. "Program has an internal problem" usually requires restarting the program.
- pron 7y agoThere is a clear distinction. RuntimeException (and descendants) is an internal problem (e.g. divide-by-zero); Error (and descendants) is a VM-internal problem (stack overflow, OOM); checked exceptions are external problems (e.g. IO exceptions).
- jgon 7y agoMaybe I am misunderstanding your point here, but isn't making RuntimeException be the base for internal problems the issue the parent poster was talking about? Divide by zero is an obvious case, but what about when invalid or misconfigured data of some type is passed into a method? This is "internal", ie inside the program, unless I am misunderstanding your terminology? It isn't an external problem, so it isn't a checked exception, which means that the caller to your method doesn't necessarily actually need to handle RuntimeException that you would throw, meaning we're back in the C world of optional error handling? I might be missing your point, but I would think that making it so that only program-external problems throw checked exceptions defeats a lot of the forced error handling power that checked exceptions grant?
- munchbunny 7y agoYou can always make input validation errors a checked exception with your own exception type. It'll accomplish what you're trying to accomplish. It's not perfectly clean because it'd be nice if you could stick it under the "IllegalArgumentException" part of the inheritance tree, but it does what you're asking for.
- 7y ago
- ciconia 7y agoThis is one of the things Ruby nails with its ensure statement that lets you clean up properly even if an exception was raised.
- WalterBright 7y ago> Resource Acquisition Is Initialization is fine It doesn't work well at all for transactions, where both A and B must succeed or neither. https://dlang.org/articles/exception-safe.html https://dlang.org/articles/exception-safe.html
- patrec 7y agoThanks, that's a nice discussion! Do you know if a similar, more granular approach (scope(exit)=~finally, scope(failure)=~catch, scope(success)=else) over go-style defer=~finally is implemented elsewhere than D?
- Measter 7y ago>Yes. Exceptions are kind of a pain, but the workarounds for not having them are worse. Passing back "result" types tends to lose the details of the problem before they are handled. Rust is on, what, their third error handling framework? Rust's non-panicking error handling hasn't really changed: you return a Result<SuccessType, ErrorType>. What has changed is the details of how to implement your ErrorType. Should it store some sort of context? What useful helper functions can there be? Things like that. What these error handling frameworks provide is macro-based code generation to implement these details, and extension traits for the helper funcitons. They don't change overall method of error handling. Or, at least, I've not seen one that does.
- stouset 7y agoGP wasn't referring to those types of exceptions. They were referring to the golang authors making escape hatches for themselves which aren't available to end users.
- a1369209993 7y agobtilly really walked into that one[0], but they meant "exceptions to the rules of the language", not "un-/anti-structured stack and/or control-flow fuckery". Most famously the lack of generics, except for anything the standard library wanted to use generics for. 0: "special cases are allowed for me but not for thee" would have been better but, y'know, hindsight.
- anonsivalley652 7y agoRust has panic (and unwind_stack) for when things are truly borked. Exceptions for error conditions are for the birds because they lead to bugs either in the code or in the compiler, and they lead to messy code. Rust has functional-style error handling where it's possible to run other code, handle or ignore errors whereas exceptions create messy try catch blocks for every caller.
- patrec 7y agoInternal (bug, e.g. divide by zero or missing function definition in a dynamic language) vs external (out-of-your-control corner case, like file not found) is an important axis. But another axis is "finality": 1. do you just want to never crash (return code) 2. sometimes crash but have the ability to deal with the problem up the callchain (exceptions in most languages) 3. sometimes crash but be able to fix the problem and continue, at the point the error occured -- not up the call stack (restart-case etc. in common lisp) 4. sometimes crash, but have a supervisor hierarchy make an informed decision if and how to restart you and things in your dependency tree (erlang) 5. crash (panic, assert, exit) and maybe have some less sophisticated but probably very complicated mechanism take care of restarting/replacing you (systemd, kubernetes etc.) This axis may not be completely orthogonal, but probably mostly is. For example resumable conditions are nice in common lisp both to deal with external stuff (no space left on device? ask user to abort or free some up, and just resume download instead of erroring out as webbrowsers do) but also to just fix problems as you run into them and continue your computation during development, including calling a function you did not define – you can just define it and resume the call to it. Sadly, the choices in most languages for this second axis are much more constrained. Erlang's supervision trees and common lisp's resumable exceptions in particular seem very useful in many scenarios but nothing else has them (well, elixir has everything erlang has, but it's still the same VM/ecosystem).
- ajross 7y agoIs that wrong, though? I mean, confounding features like exceptions are... useful. That's why they found in languages in the first place. They're just footguns when used "wrongly". So why not have an "escape" hatch for experts that isn't part of the standard/official/blessed paradigm of the language? I mean, isn't this exactly what Rust did with unsafe? Warn everyone away from it, promise that "normal" code will never need it, but... include it in the language and use it pervasively where needed in the inner workings of the runtime?
- amarshall 7y agoNo, because anyone can use `unsafe` in Rust. GP’s point was that Go can use the feature internally but no end-user can.
- ajross 7y agoThat sounds more like a legal argument than an engineering one. I mean, who cares what the mechanisms are and what the precise rules and enforcement mechanisms are? The point is there are complicated features that are OK in some contexts but not others, that this varies between problems and between languages, and that some parties make different decisions on how to make use of them even when implementing software for people who take contrasting positions. I mean... so what? Either Go is good because it lacks exceptions or it's bad for the same reason. And it's either a good decision that it uses a similar mechanism internally or it's a bad one. Both of those are arguments worth having, but they are different arguments and there is no technical reason to demand they be resolved in the same direction.
- int_19h 7y agoIt is very much an engineering argument, because writing maintainable software requires respecting contracts. If an escape hatch is private, you can't depend on it, because code doing so will break when the hatch changes (and there's no guarantee that it won't).
- 7y ago
- agumonkey 7y ago> , they created escape hatches for themselves but not the language users. man, seriously disappointing..