News for hackers. Stuff that matters.
Re:Why should I have written ZeroMQ in C, not C++ (2012) by derefr on Wednesday December 12, 2018 @07:47PM (#18668989)
Errors and exceptions are really fairly different things, and I personally appreciate languages like Erlang that have both.

"Errors" are an instance of a (perhaps-implicit) returned sum-type of a given function. Calling a function that returns a sum-type, and then not having a branch for each instantiation of that sum-type, is almost always a logic error in your code, no matter what the sum-type is.

A pretty good generic solution to "error-handling" is the Either monad, as seen in its literal form in Haskell, in Javascript as a convention for async callbacks, and in Erlang as the conventional sum-type of {ok, Val} | {error, Desc}. The Either monad is nice because you can't really "lower" monadic code by ignoring the monad and just "getting at" the result; instead, you have to "lift" your code into the realm of the monad, which requires you to specify how you'll handle each of its potential cases. (Even if your specification is simply "I expect this; if it isn't this, crash"; or "I expect this; early-return anything else to my own caller.")

"Exceptions", on the other hand, are things that by default no component of your system knows how to handle, and which usually—other than some infrastructure-level components like HTTP error handlers and "telemetry" service libraries—code just allows to bubble up until it hits the runtime and becomes a process abort. These include "runtime errors"—for example, failures of memory allocation; and "programmer logic errors"—for example, dividing by zero.

In either of these cases, the "correct" thing to do is "nothing at all", because the system itself has no idea how to handle these at runtime. The only time when these problems could have been handled was at development time, by writing more or different logic such that these exceptional circumstances would never arise in the first place. Now that you're in such a situation, though, you pretty much just want to note down what happened (so the log can be sent to a developer, who can use it to understand what the novel exceptional situation was, and then plan a change that will prevent the exceptional situation in the future) and then die quickly, before your now-invalid runtime state can do harm to things like durable stored state.

Or, to put that another way:

• an error says "I failed to do that."

• an exception says "I have noticed, while trying to do that, that no valid system-state can result, even one of failure."

Re:Why should I have written ZeroMQ in C, not C++ (2012) by electrograv on Wednesday December 12, 2018 @07:58PM (#18669046)
I don't think this alleged distinction between exceptions and errors is as clear-cut as you imply. I think this distinction is purely a convenience; a line we draw to make our lives easier because we don't really want to accept that extremely rare error cases do exist and should be reasoned about despite their rarity.

For example, you listed as some examples of exceptions that are not error: Divide by zero, failures of memory allocation.

Let's say you write a calculator GUI and I try to divide by zero in it. Exception or error?

If I applied your advise, I would have to deem this an exception, and just "just note down what happened" or "die quickly" (your words)! That is quite obviously wrong, in this example.

The correct answer would be to feed some kind of error code to the calculator's output data path, which would eventually be displayed on the screen.

Sure, I'm sure you'll come back and say now "well it depends then, and whether you consider it an exception or an error depends on the application". If you say that, you are ceding my point, because that is precisely what an 'error' (not an exception) is: a condition that may in some cases be fatal to the application, and in some cases be a part of ordinary runtime behavior.

Re:Why should I have written ZeroMQ in C, not C++ (2012) by lost953 on Wednesday December 12, 2018 @08:13PM (#18669131)
I disagree with your case, this is clearly an error because it should never get to dividing by 0.

You MUST validate user input and user input validation failures are a class of errors not exceptions.

For example look at java (which uses the opposite terminology but exact same concept). Checked exceptions are expected state that should be handled gracefully, unchecked exceptions and errors are unexpected and the handling of them is generally let someone know what happened and exit quickly.

Re:Why should I have written ZeroMQ in C, not C++ (2012) by electrograv on Wednesday December 12, 2018 @08:16PM (#18669139)
> I disagree with your case, this is clearly an error because it should never get to dividing by 0. You MUST validate user input and user input validation failures are a class of errors not exceptions.

This is bizarre: it sounds like you're disagreeing with me, by agreeing with me (that this is an error, not an exception)! For the confused (myself included), please realize that lost953's argument is considered "begging the question" (a logical fallacy [1]): You're using your a-priori assumption that 'divide by zero' is an exception, to argue that what we really should be doing here is add an if-guard before the "actual divide" occurs, so we can return an error, instead of an exception!

Otherwise, thank you for conceding that exceptions are just a category of error :)

[1] https://en.wikipedia.org/wiki/Begging_the_question

Re:Why should I have written ZeroMQ in C, not C++ (2012) by lost953 on Wednesday December 12, 2018 @08:40PM (#18669257)
No you misread my refutation, I make no claims of the errorness or exceptionness of dividing by 0, merely that your proposed example doesn't support your claim that it depends on the nature of the application. I claim that unchecked user input falls into the category of 'error' and that clearly is what has occurred in your proposed example.
Re:Why should I have written ZeroMQ in C, not C++ (2012) by fao_ on Wednesday December 12, 2018 @10:07PM (#18669578)
> I disagree with your case, this is clearly an error because it should never get to dividing by 0.

> No you misread my refutation, I make no claims of the errorness or exceptionness of dividing by 0,

Hmmm.

Re:Why should I have written ZeroMQ in C, not C++ (2012) by sethrin on Wednesday December 12, 2018 @10:27PM (#18669662)
I understand them to be saying that if execution is supposed to be halted before the division occurs, then whether the division is an exception or an error is a moot point.
Re:Why should I have written ZeroMQ in C, not C++ (2012) by adrianratnapala on Wednesday December 12, 2018 @09:30PM (#18669449)
I think the point the GP is making is that your calculator might have a function like like

   calc_div(num: Number, den: Number) -> (Error | Number):
     if den ==  0:
        return Error("division by zero")
     else
        return num / den

Now this function pre-validates the data. But it might be used as part of a much larger system. Should that system be programmed to know that `den` should always be non-zero and pre-pre-validate accordingly? Or else should it leave "calc_div" to be the expert on the rules for division?

If you take the latter approach, then the caller has to have a way of accepting the error gracefully, as a normal thing that might happen. And thus we have a div0 that is an error rather than an exception.

Re:Why should I have written ZeroMQ in C, not C++ (2012) by burfog on Thursday December 13, 2018 @02:25AM (#18670472)
Ah, but floating-point math is such fun! Here is how it might work...

den is a tiny non-zero value in an 80-bit register. It gets spilled to a 64-bit slot on the stack, rounding it to zero, while another copy of it remains in an 80-bit register. The 80-bit version is compared against zero, and is non-zero. The 64-bit version, which is zero, gets used for the division.

It is fully standards-compliant for a C compiler to do this. Some languages may specify otherwise, but often the behavior is unspecified or is implicitly the same as C.

Re:Why should I have written ZeroMQ in C, not C++ (2012) by adrianratnapala on Thursday December 13, 2018 @04:07PM (#18676124)
For many use cases, it a bad idea for a calculator program to use floating point, rather some more exact representation.

However if you do use floating point, then the kind of dangers you point out make my point even stronger. You could conceivably embed the `calc_div` function in a larger system that knew about pre-validating for div0. But if you want to deal with all possible sources of FP weirdness when doing division, then you really need to concentrate it in the "division expert": i.e. have calc_div pre validate all that stuff, and have its caller accept that errors are a normal result.

Re:Why should I have written ZeroMQ in C, not C++ (2012) by maxlybbert on Thursday December 13, 2018 @12:32PM (#18674162)
“Divide by zero” is an exception in the math library. But a GUI calculator shouldn’t pass all input to the math library without parsing it first, and figuring out what the user wants and if the user has entered a valid statement.

One guideline I follow for choosing between error codes and exceptions is “can the immediate caller do anything about this issue?” Even if the calculator does feed user input directly into the math library, the calculator can’t do anything sensible with an invalid statement. The calculator will have to “go up a level” and ask the user for help.

Re:Why should I have written ZeroMQ in C, not C++ (2012) by sk5t on Wednesday December 12, 2018 @08:55PM (#18669322)
Disagree with validating that sort of input in code you (the non-framework/BCL author) write; let the operators, functions, etc., do their own work of deciding if their operands are acceptable. Otherwise, where does it end--do you pre-test two integers to verify their product won't overflow the type declared for their product? I think you gotta let the exception happen.
Re:Why should I have written ZeroMQ in C, not C++ (2012) by derefr on Wednesday December 12, 2018 @08:25PM (#18669178)
No: as I said above, an unhandled error generates an exception. But you shouldn't be able to have an unhandled error—good type systems prevent you from compiling such code, by treating all the errors that a function can return as cases you are required to handle at the call-site. Maybe generically with a default case, but you've still gotta handle them.

Dividing by zero is an exceptional situation (at the level of the processor, even!), rather than an error, because most divisions have known, nonzero divisors; certainly, all intentional divisions do. If you do end up telling the processor to divide by zero, it is assumed that you fucked up and didn't write your program right, because user-supplied divisors are a very rare use-case compared to known-domain divisiors, and a known-domain divisor being zero is indeed programmer error.

But, even if cases where the user controls the divisor are comparatively rare, they do exist. So, even if the processor isn't going to help you, why not have the compiler generate a check for them, such that integer division would be an (Either Integer Error) kind of operation? Well—performance.

Integer division—in languages where it generates an exception—is a low-level primitive. It's meant to be fast. (In fact, all integer operations in such languages are meant to be fast single-CPU-instruction-equivalent primitives; this is also why e.g. overflow isn't checked on integer addition.) Compilers for low-level languages are expected by their users to not generate any protective code for these primitives, because that protective code would get in the way of using the primitives at the highest efficiency possible. 99% of the time, the domains of these operations are fixed at the business level, such that these errors cannot occur. And, the other 1% of the time [like when writing a calculator program], the users of these languages use "safe math" libraries built on top of these primitive operations, rather than the primitive operations themselves.

Let me put this another way, with a concrete example.

In Rust, in safe code, you can't dereference arbitrary pointers, only opaque abstracted pointers (references) given to you by the runtime, that are guaranteed by the compiler to have non-null [direct] contents. You can make the inner type of a RefCell an Option<Foo> instead of just a Foo, and then it can be null... but this kind of null is an instantiation of the Maybe monadic sum-type, and so the compiler can notice when you aren't checking for its existence and stop you.

But in Rust, in unsafe code, you can dereference an arbitrary pointer, and the result can be null. What should happen when you do so? Well, that's an exceptional situation. You asked for unsafety, and then you did the stupid thing you weren't supposed to do. There's nothing that can really save your code now.

Before the invention of "exceptions", we just called these types of errors faults. Protection faults, for example. Your code would just be killed, without the ability to do anything, because it just did something that broke the semantics of the abstract machine (the process) that the OS had the program boxed up in. The OS might be kind enough to core-dump your process's memory in the process of killing it.

Exceptions are still faults. They just let you do arbitrary other stuff as your process comes tumbling down. There's nothing you, or the runtime, can do to "save" your process, when the "error" in question is precisely that you first asked your compiler for unsafety—asked it to not prevent your code from compiling if it does something invalid—and then you went ahead and used that unsafety to do something invalid.

In Erlang land, we have a third thing: exits. Exits are faults on a small scale—they kill the actor-process that generates them, and then spread across its linked siblings and parents to kill those too, until/unless one of them is "trapping exits", at which point that actor-process will receive the exit as an informational message from the runtime rather than just dying. Most actor-processes don't trap exits; and, in fact, you can't trap an exit generated by your own actor-process, only an exit "echoed" to your actor-process from below/around you. And the only processes that do trap exits, don't attempt to "save" the actor-process that is dying. Unlike with exception-unwinding, by the time an exit is "heard" by another actor-process, the actor-process that emitted the exit is already dead. The point of trapping exits is to decide what to do after the actor-process dies—for example, starting another one of the same actor-process to replace it.

(As it happens, POSIX exit statuses fit this concept as well, though semantics like bash scripts not being `set -e` by default kind of screws that up.)

Exceptions have their place—they describe a fault-in-progress, and let arbitrary things happen as a fault causes a stack to unwind, before the process dies. Exits also have their place—they let processes react to other processes having faulted. And errors have their place—they're a branch of the Either monad that a compiler can force your code to handle.

Personally, I don't see what confusion there is between any of these. They're roughly orthogonal.

And none of them represents "something bad happened from this function call. Somebody up there on the stack, help me recover, please!" (Those would be Lisp conditions, but nobody uses those.)

Re:Why should I have written ZeroMQ in C, not C++ (2012) by general_pizza on Wednesday December 12, 2018 @09:46PM (#18669497)
If you think of it more like “how do I want to handle something bad happening?” instead of “what category does this fall under?” then I believe electrograv‘s point of not being clear-cut becomes more clear.

For example in rust most code that can fail will return a Result and thus the compiler forces you to handle that. However, that code can just as easily panic and behave like an uncaught exception would (thread exiting). An example would be the division operator and the array index operator. Both division-by-zero and out-of-bounds errors can certainly be handled by using a Result but in this case the Rust developers made a decision to use panic. Are these both exceptions bc they are handled like a typical uncaught exception or are they errors bc it’s conceivable to handle them just like a failed file open?

Re:Why should I have written ZeroMQ in C, not C++ (2012) by jodrellblank on Thursday December 13, 2018 @03:24AM (#18670732)
(Those would be Lisp conditions, but nobody uses those.)

Off-topic, but why does nobody use those? The idea of "I have an exception, I'm the best place to act on it but the worst place to decide what action to take, request for advice" sounds good, at least

Re:Why should I have written ZeroMQ in C, not C++ (2012) by saagarjha on Wednesday December 12, 2018 @08:52PM (#18669311)
> Before the invention of "exceptions", we just called these types of errors faults. Protection faults, for example. Your code would just be killed, without the ability to do anything, because it just did something that broke the semantics of the abstract machine (the process) that the OS had the program boxed up in. The OS might be kind enough to core-dump your process's memory in the process of killing it.

Does your definition of a “fault” mandate that the process be killed? I’m sure there are architectures where forcing this to occur would require checks.

Re:Why should I have written ZeroMQ in C, not C++ (2012) by derefr on Wednesday December 12, 2018 @09:03PM (#18669346)
A "fault" is usually a term for a trap/kernel-mode interrupt that results from a check at the hardware level, built into an instruction. For example, a protection fault occurs when the processor attempts to access a memory address that isn't currently mapped.

There might exist ISAs without any equivalent to faults—but I would guess that the semantics of any "faultable"-instruction-equivalent on such an ISA, would 1. actually take the slow path and use a flag register to return the status of the execution; and 2. would thus require C and C-like languages to generate shim code that e.g. checks for that flag result on every use of the dereference operator, in order to generate a userland-simulated "fault"-equivalent (i.e. an abort(3)). This is because these languages don't have any way to capture or represent the potential flag-result return value of these operations. Low-level languages expect dereferencing to raise exceptions, not return errors. There's no place for the errors to flow into.

Re:Why should I have written ZeroMQ in C, not C++ (2012) by saagarjha on Wednesday December 12, 2018 @09:11PM (#18669381)
The reason I’m asking is because we seem to be a in a conversation where we are redefining words and tying them to abstract concepts, so I wasn’t sure if by “fault” you meant “this means the processor literally faults” or “I’m going to call a fault the termination of a process when it does something illegal, named because that’s how processors usually work today”. From your response, it seems like you’ve taken the latter interpretation.

> Low-level languages expect dereferencing to raise exceptions, not return errors.

Are you talking about C? Because it doesn’t actually define anything in this case. Sure, on most OS/architecture combinations, it will trigger a memory protection violation, but this is not guaranteed and I’m sure that in these cases the compiler doesn’t literally insert checks before dereferencing anything to guarantee failure.

Re:Why should I have written ZeroMQ in C, not C++ (2012) by derefr on Wednesday December 12, 2018 @09:24PM (#18669422)
> Are you talking about C?

Yeah, the "C abstract machine" that most C-like languages rely on by the fact of relying on libraries like libc.

> Because it doesn’t actually define anything in this case.

To be clear, when I said expect, I meant expect. These languages expect invalid dereferences to fault—i.e. to cause control flow to be taken out of the program's hands. Which is to say, they expect a generated dereferencing instruction that returns to have put the system into a valid state.

But in practice, what that means is that compilers just expect dereferences to always be valid (because any dereference that returns is valid.) So they don't have to generate any check for null before doing a dereference; and they don't have to worry about having any way to represent the result of a null dereference.

Another way to say that is that dereferencing null pointers is implicit undefined behavior. There's no part of the spec that covers what would happen if you dereferenced a null pointer, because in the model as the spec lays it out, dereferencing is a happy, clean little operation that never goes wrong. (i.e. "The MMU might cause a CPU fault? Who cares! My program didn't cause that fault; it was the MMU's choice to decline to read from $0. Other MMUs could totally allow that, and then LEA would be defined for the entire domain.") Same goes for reading from unmapped regions—"it's the MMU's fault; it could have just as well returned 0, after all."

> I’m sure that in these cases the compiler doesn’t literally insert checks

Yep, you're right. Again, it's because this is what these languages expect. It's not quite the same as undefined behavior; it's that the "C abstract-machine evaluation model" was defined to assume that architectures will always have their IDIV-equivalent instruction fault if invalid. If it returns a flagged value on some arch instead, that's something the C evaluation model is not prepared to deal with.

(Technically, I believe that you'd say that a C compiler "cannot be written" for such an arch while retaining conformance to any existing C standard, and for such an arch to gain a conformant C compiler, a new C standard draft would have to be written that specifies an evaluation model for such architectures—as then the compiler could at least be conformant to that.)

Which is helpful to know: just compiling C for such an arch at all, is undefined behavior, no matter what you write ;)

Re:Why should I have written ZeroMQ in C, not C++ (2012) by saagarjha on Thursday December 13, 2018 @07:08AM (#18671704)
I’m not sure I follow your description of undefined behavior, which seems to me to deviate from the standard’s? Correct me if I’m wrong, but to me it seems like you’re saying something along the lines of “the C standard ‘expects’ that null dereferences fault, and ‘assumes’ division by zero to trap; it is impossible to write a standards compliant compiler for architectures where this is not true”. If so, let me know an I’ll explain why I disagree; otherwise, it would be nice if you could clarify what you actually meant.
Re:Why should I have written ZeroMQ in C, not C++ (2012) by emsy on Wednesday December 12, 2018 @08:38PM (#18669252)
The error bubbling up is a good thing. It at least allows you to find missing error checks in the underlying code. Good luck trying to find an unchecked return code. It seems you mix up problems innate to exceptions (like messing with the control flow) with user problems (unchecked errors/exceptions).
Re:Why should I have written ZeroMQ in C, not C++ (2012) by electrograv on Wednesday December 12, 2018 @08:45PM (#18669279)
> Good luck trying to find an unchecked return code.

We're talking about alternate programming languages that handle errors vs exceptions differently, so it's only fair if we consider a language designed from the start not to need exceptions.

So let's take Rust, for example: In Rust, it's not a matter of luck at all: You declare whether your return code may be ignored; if not, it's a compile error not to use it.

Re:Why should I have written ZeroMQ in C, not C++ (2012) by emsy on Thursday December 13, 2018 @08:33AM (#18672158)
Fair enough, error checks should always be enforced. I think errors and exceptions are both valid concepts that solve overlapping problem areas. Java has checked exceptions, so you can statically enforce exception handling.
Re:Why should I have written ZeroMQ in C, not C++ (2012) by Gibbon1 on Wednesday December 12, 2018 @10:22PM (#18669636)
In c you can declare a function with __attribute__((warn_unused_result))
Re:Why should I have written ZeroMQ in C, not C++ (2012) by josefx on Thursday December 13, 2018 @04:30AM (#18671029)
Not even used for the C standard library and that would lead to warning spam as every printf call could fail and if there is anything I have never seen it is C code checking the return value of printf.
Re:Why should I have written ZeroMQ in C, not C++ (2012) by maxlybbert on Thursday December 13, 2018 @12:37PM (#18674232)
You would only mark functions where it’s a bad idea to return the error code that way. Printf isn’t a function like that, and fprintf probably isn’t (use case of printing to stderr), but fwrite is.
Re:Why should I have written ZeroMQ in C, not C++ (2012) by maxlybbert on Saturday December 15, 2018 @01:29PM (#18689626)
... “bad idea to ignore the error code” ...

It took a few days to notice.

Re:Why should I have written ZeroMQ in C, not C++ (2012) by Gibbon1 on Thursday December 13, 2018 @05:20AM (#18671227)
> C standard library

is an mostly an ancient POS

Re:Why should I have written ZeroMQ in C, not C++ (2012) by hoekit on Wednesday December 12, 2018 @08:38PM (#18669254)
Nicely and clearly put. Doesn't really matter if it is merely a convenience as @electrograv said, i find this a very pragmatic way of looking and handling at the matter.