4 ms·
The problem is not "forgetting about it", it's that it increases the possible values of your working set. If you have 3 variables, each of which can be in 10 s
by mgaunard 4y ago
The problem is not "forgetting about it", it's that it increases the possible values of your working set.
If you have 3 variables, each of which can be in 10 states, that's 10^3 states your program can be in.
If instead you have 11 states because all your variables are actually unions with an error, that's 11^3 states (assuming all error states are equivalent to a single state).
Now in practice it's even worse since what you care about isn't the possible states of your values, but rather how many different paths you have in your control flow to handle them.
Then you're comparing 1 (none of my values are in an erroneous state) with 2^3=8 (any of my values can be in an erroneous state or not).
What exceptions do is enforcing that your working set does not have to encode any erroneous states, preventing the combinatorial explosion of states, which of course is a net win, there isn't really any valid argument that can be made against it.
Where people are debating is that sometimes you do want errors to be part of your working set, in which case you shouldn't use exceptions. But choice is difficult for some, especially those seeking absolute doctrines.
> It very much isn't, part of the system programming workload is to provide reusable components.
That's already somewhat dubious, since a lot of system programming tasks are really purpose-built for a usecase or for specific hardware, and regardless, there is nothing about that which has anything to do with interfacing with C.
I do a lot of system programming and I write it all in C++, which has a lot of advantages over C beyond exceptions.
- zozbot234 4y ago> What exceptions do is enforcing that your working set does not have to encode any erroneous states You do that by having types that encode a guaranteed non-erroneous state. It's not like exceptions are doing anything all that different, they're just trying to establish that guarantee in a language where variant record types and pattern matching are not first-class facilities. This is something where C and C++ actually regressed from PASCAL, which did have support for variant records.
- mgaunard 4y agoA variant does not make that guarantee, it just segregates it.
- jstimpfle 4y agoSame for exceptions really. Exceptions don't give any guarantee of non-erroneous state. The guarantees that you're talking about actually come from how construction and deconstruction work in C++ (note how it plays with early returns just fine, no exceptions needed). And these construction semantics can be implemented with variant types as well, it's completely unrelated.
- mgaunard 4y agoThe prevent the control flow from continuing in that direction, which prevents those variables from ever existing. Early return is nothing like exceptions. Early returns needs to return something which passes the problem to someone else. It's also a choice to do it at all.
- jstimpfle 4y agoYou're completely missing my point. The point is that both prevent the control flow from continuing in that direction. Both prevent the variables declared later to ever "exist".
- anonymoushn 4y ago> If instead you have 11 states because all your variables are actually unions with an error, that's 11^3 states (assuming all error states are equivalent to a single state). This isn't what actually happens though, what actually happens is that people declare local and member variables that are the_type_i_actually_want instead of Result<err, the_type_i_actually_want> and bubble up their errors like they would exceptions. So they get the benefits that you've claimed, but they don't need to pay the runtime cost of not-thrown exceptions that C++ users have to pay, they don't have to use external tools to tell them that functions they're using can throw exceptions, and they don't have to enjoy the wonders of Java where checked exceptions in function signatures regularly prevent the use of streams.
- menaerus 4y agoYou're conflating recoverable errors (Result in Rust, status codes or std::expected in C++) with the non-recoverable errors (panic in Rust, exceptions in C++). If we were to compare Rust panics vs C++ exceptions, then handling of Rust panics is much less flexible. From what I understand, it's essentially a std::abort and it can be hardly used otherwise, which is only a subset of how C++ exceptions can be commonly used too. If we were to compare Rust Result vs C++ std::expected, they boil down to pretty much the same with the difference of Rust requiring the call-site to unconditionally check for the return value. That may or may not be preferable in every situation. > they don't need to pay the runtime cost of not-thrown exceptions that C++ users have to pay Had this been true, which in 99% of cases it isn't unless you can support your claim, do you mind sharing how Rust implemented their zero-cost panics?
- ginsider_oaks 4y agoThey're not conflating. mgaunard says: > The alternative that the parent said was making all of your state be a union > with some kind of error, and making sure all accesses handle the fact the > variable might be in a erroneous state. This is exactly what `Result` is in Rust. While I haven't used Rust, it seems that panics are generally discouraged and only used as a last resort whereas exceptions are more commonly used in C++ and Java.
- jstimpfle 4y agoYour reasoning is off. You don't have "10^3" states if you always unwrap the return values at the call sites (which implies returning if it fails). It's literally the same as exceptions, just that the errors get encoded by (re-)using the type system. You'll have the exact same types for your local variables -- the only difference being that you would put a '?' (or similar) after function calls, to unwrap the return values. The advantage of this ADT approach is that you can store error unions more permanently when it makes sense. It is not additional syntax, unlike exceptions. In that sense ADTs are the simpler approach of the two. If there is any "explosion of complexity", then it is exceptions where you get that -- because you have to express your code using multiple mechanisms (types vs exceptions), and possibly have to switch between the two when refactoring. I say that as someone who doesn't think highly of either approach. In my view, plain error values are fine, there isn't any clever language solution needed. If you find yourself checking return values a lot (as opposed to storing error values in handles and checking them at strategic locations), that can hint an architectural problem.
- mgaunard 4y agoBy unwrapping and returning, you're creating another path down the control flow of your program, which also propagates to your callee, since you have to return an error. Exceptions don't do that, they stop the flow entirely, then match it to a point arbitrarily higher on the stack, and resume after that whole sub-tree has been destructed. They're also much more efficient than branching and maybe returning on the result of every single function call.
- masklinn 4y ago> Exceptions don't do that Exceptions actually do that, except hidden and unsignaled. > They're also much more efficient than branching and maybe returning on the result of every single function call. Not when actually taken.
- dureuill 4y ago> Exceptions actually do that, except hidden and unsignaled. Which IMO is good in exactly one situation: when raising the exception means that the program contains a bug. Using panics (ah sorry... exceptions) in this case is justified as it should be really exceptional (if there is a bug anyway we have more pressing problems than performance) and in the absence of bug if we were to use a Result type it would mean we would have a "BugError" variant that is actually dead code everywhere where the program is not buggy. So in my opinion a correct approach is to unwrap whenever you have an invariant that guarantees that there should be a value, with a panic handler set at the boundary of the logical task to fail the entire logical task in case there is a bug. A logical task can be an asynchronous light task, a thread, or the whole process depending on the situation. I much prefer it not being the whole process when the process is e.g. a web server or a word processor (and the failure occurred somewhere in an ancillary function)
- fckgnad 4y ago>What exceptions do is enforcing that your working set does not have to encode any erroneous states, preventing the combinatorial explosion of states, which of course is a net win, there isn't really any valid argument that can be made against it. A combinatorial explosion of states is not a bad thing. Integers in C++ for example have 4294967296 possible states. Programming is not descending into complete chaos just because one of the fundamental types has more possible states then the human brain is capable of handling. You're describing using exceptions as a catch all fail-safe. It's isomorphic to the the "else" statement in your standard if-else structure which is one of the techniques people use to handle the 4294967296 possible states of int. See example code below on this amazing technique I use to deal with 4294967296 possible branching possibilities: if(x == 0){ // do something } else { //handle all 4294967295 other states. } >Where people are debating is that sometimes you do want errors to be part of your working set, in which case you shouldn't use exceptions. But choice is difficult for some, especially those seeking absolute doctrines. In every other engineering field you do want this as part of your design. You want to know about every possible state your system can be in and handle the states explicitly. Unknown states that are not explicitly encoded into an engineering design is typically a Bad thing. That is not to say you should design your system and not acknowledge the possibility of an unknown state. You need fail-safes like exceptions to handle these unknown states. But make no mistake, it's not good to have fail-safes regularly executing to catch a bunch of states you failed to encode into your system. A good example of this is corrected design of the MCAS on the boeing 737 max. The MCAS should not use a fail-safe handle the crash modes we are now well aware about. The MCAS should explicitly be encoded with our knowledge about the new possible error modes. I certainly don't want to sit in a plane where this hasn't been done. I will also say that much of programming doesn't need the level of safety other engineering products need. Shipping products faster at the cost of quality is something unique to software as the quality can be improved AFTER shipping, so that is not to say your way of using exceptions to catch unknown states (or states not explicitly encoded into the system) is completely wrong; but is certainly not best practice or ideal.
- masklinn 4y ago> If you have 3 variables, each of which can be in 10 states, that's 10^3 states your program can be in. > If instead you have 11 states because all your variables are actually unions with an error, that's 11^3 states (assuming all error states are equivalent to a single state). > Now in practice it's even worse since what you care about isn't the possible states of your values, but rather how many different paths you have in your control flow to handle them. You're really demonstrating that you have no clue about the subject and refuse to think about it. If the current function does not deal to specifically deal with erroneous results (aka it would be a passthrough for exceptions) then it unifies the error states into one, by either pruning their branches through early-returning, or unifying the triplet of results into a result of triplet. Hence you don't have 11^3 states but 10^3 + 1. > What exceptions do is enforcing that your working set does not have to encode any erroneous states, preventing the combinatorial explosion of states, which of course is a net win, there isn't really any valid argument that can be made against it. The problem is that none of that is actually true, you're literally inventing combinatorial explosions which effectively don't exist. Unless they would have to in all cases at which point exception would lead to a significantly worse combinatorial explosion, because exceptions would not allow representing the product of 11 states as just that, and instead would need 20^3 states as every possible value would have to be paired with two error states, success and failure. > That's already somewhat dubious It really is not. > there is nothing about that which has anything to do with interfacing with C. The C (or system) ABI is the linga franca of inter-language communication, unless you decide to pay for a network cost. > I do a lot of system programming and I write it all in C++, which has a lot of advantages over C beyond exceptions. And plenty of drawbacks as well. But if all you know is C and C++ and you see the entire world through that lens, I can see why you're missing most of the field, you're essentially blind.
- mgaunard 4y agoException prevent the control flow from continuing, which prevents the creation of those states which happens further down. I find your tone too inadequate to engage further with you though.
- deschutes 4y agoThis doesn't track at all for me. Rust provides strong guarantees around accessing discriminated unions. The net effect of which is that the code you write has the "railway style" error handling that you get with exceptions in the trivial case (propagate the error). It even has a convenient syntactic shorthand for this `?`. In non-trivial cases they are equivalent too. For example, collections need to maintain at a minimum a valid state in the presence of types with exception-throwing (fallible) constructors. This is a mess with or without exceptions in basically the same way. It's such a mess that the C++ standard allows for unspecified behavior of `std::vector::push_back` if the contained type has a throwing move constructor. Throwing move constructors are of course ridiculous but nonetheless allowed. And that I would say is the biggest flaw with exceptions: they presume the fallibility of everything by default. This is not only brain damaging, it actively creates situations where there are no good options.