June 06, 2022

On Monday, 6 June 2022 at 04:59:05 UTC, Ola Fosheim Grøstad wrote:

>

For instance if a sort function fails, then you can call a slower sort function.

Or in terms of actors/tasks: if one actor-solver fails numerically, then you can recover and use a different actor-solver.

Those are not places where you would put an assert.

The only place to put an assert is when you know there is no recovery.

Like you have been arguing, there aren't many places like that.

So don't use it.


9 out of 10 times when I see an assert in code review I ask the author to reconsider. Often it only requires a little tweak.

I guess you could say I have found asserts to be misused.

June 06, 2022

On Monday, 6 June 2022 at 06:14:59 UTC, Sebastiaan Koppe wrote:

>

Those are not places where you would put an assert.

The only place to put an assert is when you know there is no recovery.

No, asserts are orthogonal to recovery. They just specify the assumed constraints in the implementation of the algorithm. You can view them as comments that can be read by a computer and checked for that specific function.

For instance you can have a formally proven reference implementation full of asserts, then one optimized version where you keep critical asserts or just the post condition. If the optimized version fails, then you can revert to the reference (with no or few asserts, because it is already formally verified).

There is nothing wrong with having many asserts or asserts you «know» to hold. They are helpful when you modify code and datastructures.

Maybe one could have more selective ways to leave out asserts (e.g. based on revision) so that you remove most asserts in actors that has not changed since version 1.0 and retain more asserts in new actors.

Also, if you fully check the full post condition (in @safe code) then you can remove all asserts in release as they are inconsequential.

So the picture is more nuanced and it should be up to the programmer to decide, but maybe a more expressive and selective regime is useful.

June 06, 2022

On Monday, 6 June 2022 at 06:56:46 UTC, Ola Fosheim Grøstad wrote:

>

On Monday, 6 June 2022 at 06:14:59 UTC, Sebastiaan Koppe wrote:

>

Those are not places where you would put an assert.

The only place to put an assert is when you know there is no recovery.

No, asserts are orthogonal to recovery. They just specify the assumed constraints in the implementation of the algorithm. You can view them as comments that can be read by a computer and checked for that specific function.

I guess an informal way to express this is:

Asserts are comments that you would need to make when explaining why the algorithm works to another person (or to convince yourself that it works).

As far as unnecessary asserts, it would be nice to have something more powerful than static assert, something that could reason about runtime issues that are simple and issue errors if it could not establish it. E.g.:

int i = 0;
…later…
i++;
…much later…
compiletime_assert(i>0);
June 06, 2022

On 6/6/22 12:59 AM, Ola Fosheim Grøstad wrote:

>

On Sunday, 5 June 2022 at 23:57:19 UTC, Steven Schveighoffer wrote:

>

It basically says "If this condition is false, this entire program is invalid, and I don't know how to continue from here."

No, it says: this function failed to uphold this invariant. You can perfectly well recover if you know what that function touches.

For instance if a sort function fails, then you can call a slower sort function.

If it's an expected part of the sorting algorithm that it may fail to sort, then that's not an Error, that's an Exception.

>

Or in terms of actors/tasks: if one actor-solver fails numerically, then you can recover and use a different actor-solver.

If the condition is recoverable => Exception. If it is not recoverable => Error.

>

An assert says nothing about the whole program.

An assert says that something is wrong, and this was not expected or foreseen. It says that the programmer cannot attribute exactly where this went wrong because otherwise, he would have accounted for it, or thrown an Exception instead (or some other mitigation). Anything from memory corruption, to faulty hardware, to bugs in the code, could be the cause.

>

An assert only says that the logic of that particular function is not meeting the SPEC.

That's one possible reason. But if you are planning on possibly not meeting the spec (such as your sort example), that is a different story.

>

Only the programmer knows if recovery is possible, not the compiler.

Exactly. Use Exceptions if it's recoverable, Errors if it's not.

>

A failed assert is not implying undefined behaviour in @safe code.

A failed assert could be because of undefined behavior. It doesn't imply it, but it cannot be ruled out.

-Steve

June 06, 2022

On Monday, 6 June 2022 at 15:54:16 UTC, Steven Schveighoffer wrote:

>

If it's an expected part of the sorting algorithm that it may fail to sort, then that's not an Error, that's an Exception.

No, it is not expected. Let me rewrite my answer to Sebastiaan to fit with the sort scenario:

For instance, you may have a formally verified sort function, but it is too slow. So you optimize one selected bottle neck, but that cannot be verified, because verification is hard. That specific unverified softspot is guarded by an assert. The compiler may remove it or not.

Your shipped product fails, because the hard to read optimization wasn't perfect. So you trap the thrown assert and call the reference implementation instead.

The cool thing with actors/tasks is that you can make them as small and targeted and revert to fallbacks if they fail.

(Assuming 100% @safe code.)

>

It says that the programmer cannot attribute exactly where this went wrong because otherwise, he would have accounted for it, or thrown an Exception instead (or some other mitigation).

He can make a judgement. If this happened in a safe pure function then it would most likely be the result of what is what meant to do: check that the assumptions of the algorithm holds.

>

Anything from memory corruption, to faulty hardware, to bugs in the code, could be the cause.

That is not what asserts check! They will be removed if the static analyzer is powerful enough. All the information to remove the assert should be in the source code.

You are asserting that given all the constraints of the type system then the assert should hold.

Memory corruption could make an assert succeed when it should not, because then anything can happen! It cannot catch memory corruption reliably because it is not excluded from optimization. You need something else for that, something that turns off optimization for all asserts.

>

Exactly. Use Exceptions if it's recoverable, Errors if it's not.

This is what is not true, asserts says only something about the algorithm it is embedded in, it says that the algorithm makes a wrong assumption, and that is all. It says nothing about the calling environment.

>

A failed assert could be because of undefined behavior. It doesn't imply it, but it cannot be ruled out.

And a successful assert could happen because of undefined behaviour or optimization! If you want these types of guards then you need to propose a type of asserts that would be excluded from optimization. (which might be a good idea!)

In the case of UB anything can happen. It is up to the programmer to make that judgment based on the use scenario. It is a matter of probabilisitic calculations in relation to the use scenario of the application.

As I pointed out elsewhere: «reliability» has to be defined in terms of the use scenario by a skilled human being, not in terms of some kind of abstract thinking about compiler design.

June 06, 2022

On Monday, 6 June 2022 at 16:15:19 UTC, Ola Fosheim Grøstad wrote:

>

On Monday, 6 June 2022 at 15:54:16 UTC, Steven Schveighoffer wrote:

>

If it's an expected part of the sorting algorithm that it may fail to sort, then that's not an Error, that's an Exception.

No, it is not expected. Let me rewrite my answer to Sebastiaan to fit with the sort scenario:

Let me sketch up another scenario. Let's say I am making an online game and I need early feedback from beta-testers. So I run my beta-service with lots of asserts and logging, when actors fail I discard them and relaunch them.

If the server went down on the first assert I wouldn't be able to test my server at all, because there would be no users willing to participate in a betatest where the server goes down every 20 seconds! That is a very bad high risk-factor, that totally dominates this use scenario.

An engineer has to fill words such as «reliability», «utility», «probability» and «risk» with meaning that match the use scenario and make deliberate choices (cost-benefit-risk considerations). That includes choosing an actor model, and each actor has to prevent failure from affecting other actors. (by definition of «actor»).

June 06, 2022

On 6/6/22 12:15 PM, Ola Fosheim Grøstad wrote:

>

On Monday, 6 June 2022 at 15:54:16 UTC, Steven Schveighoffer wrote:

>

If it's an expected part of the sorting algorithm that it may fail to sort, then that's not an Error, that's an Exception.

No, it is not expected. Let me rewrite my answer to Sebastiaan to fit with the sort scenario:

For instance, you may have a formally verified sort function, but it is too slow. So you optimize one selected bottle neck, but that cannot be verified, because verification is hard. That specific unverified softspot is guarded by an assert. The compiler may remove it or not.

Your shipped product fails, because the hard to read optimization wasn't perfect. So you trap the thrown assert and call the reference implementation instead.

The cool thing with actors/tasks is that you can make them as small and targeted and revert to fallbacks if they fail.

(Assuming 100% @safe code.)

Then that's part of the algorithm. You can use an Exception, and then handle the exception by calling the real sort. If in the future, you decide that it can properly sort with that improvement, you remove the Exception.

That is different from e.g. using a proven algorithm, like quicksort, but failing to implement it properly.

-Steve

June 06, 2022

On Monday, 6 June 2022 at 17:52:12 UTC, Steven Schveighoffer wrote:

>

Then that's part of the algorithm. You can use an Exception, and then handle the exception by calling the real sort. If in the future, you decide that it can properly sort with that improvement, you remove the Exception.

That is different from e.g. using a proven algorithm, like quicksort, but failing to implement it properly.

No? Why do you find it so? Adding a buggy optimization is exactly failing to implement it properly. There is a reference, the optimization should work exactly like the reference, but didn't.

Using asserts in @safe code should be no different than using asserts in Python code.

Python code <=> safe D code.

Python library implemented in C <=> trusted D code.

There is no reason for D to undercut users of @safe code. If anything D should try to use @safe to provide benefits that C++ users don't get.

June 06, 2022

On Monday, 6 June 2022 at 18:08:17 UTC, Ola Fosheim Grøstad wrote:

>

There is no reason for D to undercut users of @safe code.

(Wrong usage of the term «undercut», but you get the idea…)

June 07, 2022

On Friday, 3 June 2022 at 23:40:50 UTC, Steven Schveighoffer wrote:

>

During the last beerconf, I wrote a short blog post about how Error and Exception are different, and why you should never continue after catching Errors.

I know the thematics but I still wonder why we only have scope(failure) then? Anywhere where you will use this shiny thing with a return statement will also catch any error that have occurred. scope(exit) doesn't allow return statements, thus the only properly clean design would be an additional scope(exception) guard.