Comparing Exceptions and Errors

Steven Schveighoffer schveiguy at gmail.com
Sun Jun 5 01:43:06 UTC 2022


On 6/4/22 6:56 PM, kdevel wrote:
> On Saturday, 4 June 2022 at 16:55:31 UTC, Steven Schveighoffer wrote:
> [...]
>> The point of an `Error` is that your code can assume it cannot happen. 
>> If it does happen, the code is invalid.
> 
> According to my favorite dictionary "assume" means "take for granted" 
> [1]. If `Error`s may happen how can code (or its author) "assume" that 
> `Error`s cannot happen?

You don't assume it, you guarantee it. You are expected to provide a 
guarantee to the compiler that your code won't throw these errors.

But you aren't perfect, and so maybe you make a mistake, and trigger an 
Error. The compiler handles this unexpected condition by unwinding the 
stack back to the main function, printing the error and exiting, so you 
can go fix whatever mistake you made.

It's kind of like a segfault. There's no valid reason to read memory you 
don't own (yes, I know you can use segfaults to trigger loading of 
memory, I'm not talking about that kind of segfault). So what do you do 
when an unexpected segfault happens? You crash the program, and exit. In 
this case, the language is giving you by default a hint about where it 
occurred, and if you desire, you can get more information by catching 
the error where you want and doing more checks, etc.

>> This is reflected in the fact that the compiler will omit cleanup code 
>> if an `Error` is thrown (it can assume that it will never happen).
> 
> But instead the compiler should *emit* the cleanup code and we would not 
> have to discuss here carefully avoiding to name the root cause of all 
> this entanglements.

A compiler *could* do this, and in fact, the compiler used to do this. 
But I think you are still not supposed to continue execution. I'm not 
sure what a compiler might assume at this point, and I unfortunately 
can't find in the language specification where it states this. It might 
not be in there at all, the spec is sometimes lacking compared to the 
implementation.

However, I did ask Walter about this last beerconf, and he said to treat 
a throw/catch of an error like a goto, anything can happen.

>> The point of using `Error` is for a last resort check for program 
>> correctness (because you failed to validate the input before getting 
>> to that point).
> 
> If the code threw an `Exception` instead of an `Error` everything would 
> be fine.

I think the point of Errors is that you can remove them for efficiency. 
In other words, just like asserts or bounds checks, they are not 
expected to be part of the normal working program. Exceptions are part 
of the program, and provide a different mechanism of handling error 
conditions.

>> [...]
>> A great example are range functions. Often times you see at the 
>> beginning of any `popFront` method the statement `assert(!empty);`. 
>> This is universally accepted, as you shouldn't be calling `popFront` 
>> if you haven't checked for `empty`.
> 
> Yep.
> 
> ```
> core.exception.AssertError@[...]linux/bin64/../../src/phobos/std/range/primitives.d(2280): 
> Attempting to popFront() past the end of an array of int
> ```
> 
> I see no difference to the potentially invalid array index case. It 
> would ease the use of the range if it threw an `Exception`.

But it's possible to turn off asserts and make the code run faster. I 
personally never turn them off on certain programs (web server) because 
the penalty is not noticeable enough. But if these were Exceptions, they 
*could not be turned off*.

Consider the normal flow of a range in a foreach loop, it's:

```d
// foreach(elem; range)
for(auto r = range; !r.empty; r.popFront) {
     auto elem = r.front;
}
```

If both `popFront` and `front` also always call `empty` you are calling 
`empty` 3 times per loop, with an identical value for the 2nd and 3rd 
calls. Having the assert allows diagnosing invalid programs without 
crashing your program, but also allowing full performance when you want it.

Phobos' `RedBlackTree` has an `invariant` which walks the entire RBT and 
validates the red-black property holds *before and after every method 
call*. This is not what you would want for performant code as it 
completely destroys the complexity guarantees. Yet it's there to help 
diagnose problems with RBT if you are working on modifying it. These 
kinds of checks are to help the developer prove their code is correct 
without having to continually prove it's correct for normal use.

-Steve


More information about the Digitalmars-d-learn mailing list