Thread overview
RFC: Change what assert does on error
23 hours ago
Sönke Ludwig
23 hours ago
Timon Gehr
21 hours ago
Derek Fawcus
20 hours ago
Timon Gehr
1 day ago

Hello!

I've managed to have a chat with Walter to discuss what assert does on error.

In recent months, it has become more apparent that our current error-handling behaviours have some serious issues. Recently, we had a case where an assert threw, killed a thread, but the process kept going on. This isn't what should happen when an assert fails.

An assert specifies that the condition must be true for program continuation. It is not for logic level issues, it is solely for program continuation conditions that must hold.

Should an assert fail, the most desirable behaviour for it to have is to print a backtrace if possible and then immediately kill the process.

What a couple of us are suggesting is that we change the default behaviour from throw AssertError.
To: printBacktrace; exit(-1);

There would be a function you can call to set it back to the old behaviour. It would not be permanent.

This is important for unittest runners, you will need to change to the old behaviour and back again (if you run the main function after).

Before any changes are made, Walter wants a consultation with the community to see what the impact of this change would be.

Does anyone have a case, implication, or scenario where this change would not be workable?

Destroy!

1 day ago

On Sunday, 29 June 2025 at 18:04:51 UTC, Richard (Rikki) Andrew Cattermole wrote:

>

Should an assert fail, the most desirable behaviour for it to have is to print a backtrace if possible and then immediately kill the process.

What a couple of us are suggesting is that we change the default behaviour from throw AssertError.
To: printBacktrace; exit(-1);

Full agreement.

-Steve

23 hours ago
Am 29.06.2025 um 20:04 schrieb Richard (Rikki) Andrew Cattermole:
> What a couple of us are suggesting is that we change the default behaviour from ``throw AssertError``.
> To: ``printBacktrace; exit(-1);``


This will be a serious issue for GUI applications where stderr will typically just go to /dev/null and then the application just inexplicably exits (an issue that we currently already encounter on user installations and so far it has been impossible to track down the source). Instead of `exit(-1)`, a much better choice would be `abort()`, which would at least trigger a debugger or the system crash report handler.

Regarding the assertion error message and the backtrace, it would be nice if there was some kind of hook to customize where the output goes. Generally redirecting stderr would be a possible workaround, but that comes with its own issues, especially if there is other output involved.
23 hours ago
On 30/06/2025 7:16 AM, Sönke Ludwig wrote:
> Am 29.06.2025 um 20:04 schrieb Richard (Rikki) Andrew Cattermole:
>> What a couple of us are suggesting is that we change the default behaviour from ``throw AssertError``.
>> To: ``printBacktrace; exit(-1);``
> 
> 
> This will be a serious issue for GUI applications where stderr will typically just go to /dev/null and then the application just inexplicably exits (an issue that we currently already encounter on user installations and so far it has been impossible to track down the source). Instead of `exit(-1)`, a much better choice would be `abort()`, which would at least trigger a debugger or the system crash report handler.

For posix that would be ok.

https://pubs.opengroup.org/onlinepubs/9699919799/functions/abort.html

The issue is Windows.

https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/abort?view=msvc-170

"Products must start up promptly, continue to run and remain responsive to user input. Products must shut down gracefully and not close unexpectedly. The product must handle exceptions raised by any of the managed or native system APIs and remain responsive to user input after the exception is handled."

https://learn.microsoft.com/en-us/windows/apps/publish/store-policies#104-usability

Hmm ok, technically we have no ability to publish to Microsoft store, regardless of what we do here joy.

Okay, abort instead of exit.

> Regarding the assertion error message and the backtrace, it would be nice if there was some kind of hook to customize where the output goes. Generally redirecting stderr would be a possible workaround, but that comes with its own issues, especially if there is other output involved.

There is a hook.

https://github.com/dlang/dmd/blob/master/druntime/src/core/exception.d#L531

Set the function and you can do whatever you want.

23 hours ago
On 6/29/25 20:04, Richard (Rikki) Andrew Cattermole wrote:
> 
> Should an assert fail, the most desirable behaviour for it to have is to print a backtrace if possible and then immediately kill the process.
> 
> What a couple of us are suggesting is that we change the default behaviour from ``throw AssertError``.
> To: ``printBacktrace; exit(-1);``

I don't want this, it's highly undesirable. I agree that silently killing only the single thread is terrible, but that's not something that affects me at the moment.

I guess you could kill the process instead of killing just the single thread, but breaking the stack unrolling outright is not something that is acceptable to me.
22 hours ago
On Discord Timon has demonstrated two circumstances that kill this.

1. Error will still do cleanup in some cases (such as scope exit).

2. Contract inheritance catches AssertError, and we can't reliably swap that behavior.

The conclusions here is that:

1. The Error hierarchy is recoverable, it differs from Exception by intent only.

2. ``nothrow`` cannot remove unwinding tables, its purpose is logic level Exception hierarchy denotation. If you want to turn off unwinding there will need to be a dedicated attribute in core.attributes to do so.

3. The Thread abstraction entry point needs a way to optionally filter out Error hierarchy. Using a hook function that people can set, with default being kill process.

4. assert is a framework level error mechanism, not "this process can't continue if its false". We'll need something else for the latter, it can be library code however.

I know this isn't what everyone wants it to be like, but this is where D is positioned. Where we are at right now isn't tenable, but where we can go is also pretty limited. Not ideal.

21 hours ago

On Sunday, 29 June 2025 at 18:04:51 UTC, Richard (Rikki) Andrew Cattermole wrote:

>

What a couple of us are suggesting is that we change the default behaviour from throw AssertError.
To: printBacktrace; exit(-1);

I have no issue with the suggestion.

I simply note that on posix systems, the return value usually gets mask to 8 bits, so the above is identical to exit(255).

DF

20 hours ago
On 6/29/25 23:44, Derek Fawcus wrote:
> On Sunday, 29 June 2025 at 18:04:51 UTC, Richard (Rikki) Andrew Cattermole wrote:
>> What a couple of us are suggesting is that we change the default behaviour from ``throw AssertError``.
>> To: ``printBacktrace; exit(-1);``
> 
> I have no issue with the suggestion.
> 
> I simply note that on posix systems, the return value usually gets mask to 8 bits, so the above is identical to exit(255).
> 
> DF

That particular aspect actually would be an improvement I think. One of the cases where I am catching `Throwable` is just:

```d
try{
    ...
}catch(Throwable e){
    stderr.writeln(e.toString());
    import core.stdc.signal:SIGABRT;
    return 128+SIGABRT;
}
```

This can be useful to distinguish assertion failures from cases where my type checker frontend just happened to find some errors in the user code.

It's a bit weird that an `AssertError` will give you exit code 1 by default. I want to use that error code for different purposes, as is quite standard.