www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - The solution to "Error handling"...

reply Dennis <dkorpel gmail.com> writes:
...is testing your software. 😉

There's no shortage of interesting discussions about Error vs. 
Exception, throwing vs returning, floating point NaN, UTF 
replacement chars, or the 'correct' error handling mechanism in 
general.

The CPU doesn't care about any of this, it just executes 
instructions to move memory around. The user doesn't care about 
any of this either, they just want a program that gets the job 
done. Your task as a programmer is to generate the CPU 
instructions that do the job the user wants as best as you can. 
If you find an abstraction that helps handling errors, then use 
it by all means. But beware that premature abstractions always 
get things wrong. It's easier to write straightforward code first 
and see which repetitive patterns naturally emerge from that.

The other day my X11 desktop application failed to create a 
graphics context, and as a result it printed a crappy error 
message and crashed. But wait, I thought I was meticulously 
checking the return values of al my `glx` calls and returning 
gracefully? Turns out that before returning the `null` context, 
X11 calls a default error callback that prints something to the 
console and aborts. To fix the bad UX, I set `XSetErrorHandler` 
to my own callback and printed a better error message instead.

AHA! X11 has a C interface, so this is all the fault of C's lack 
of good error handling language features. If only they used 
Exceptions, right? Well, the browser version of my app (running 
on top of Javascript APIs) also likes to randomly error, for 
example when the CDN provides and old cached .wasm module instead 
of the new one. The result: a flooded browser console (which  is 
hidden by default), while the page stays blank and unresponsive.

If you catch an Exception and simply show the message in a GUI, 
the error is usually unhelpful. A recent one I got from Phobos 
was "Positive Conversion Overflow". Without attaching more 
information to Exceptions in intermediate catch blocks, the error 
is devoid of context. (Unless you print the stack trace but it's 
not like a regular user can make any sense of that)

But wait, I heard Exceptions are also considered bad these days, 
the current trendy thing is returning `Result<Value, Error>` 
types, like Rust does. I don't have that much Rust experience, 
but I have seen code full of `result.expect("quick message")` 
(which panics and aborts the program on error) to satisfy the 
compiler's type checks, with the idea "it's a quick script, I 
can't be bothered to do proper error handling for this". So you 
get extra boilerplate in the code, but the UX is no better than a 
C library with an error callback that prints and aborts, like X11.

My personal takeaway is that regardless of what language features 
you use for error handling, you rarely get it right first try. 
You have to *test your software* by triggering  error conditions 
and observing what the UX of that is. Then it becomes immediately 
obvious where the error should have been handled and what 
information should be attached. With concrete feedback, fixing 
your code becomes so much easier.

Of course, when you have hundreds of error conditions, this 
becomes rather tedious. This is where [Walter is completely 
right](https://forum.dlang.org/post/10utksm$1h2t$1 digitalmars.com) that the
best 'error handling' is *no handling*.

In [The Easiest Way To Handle Errors Is To Not Have 
Them](https://www.dgtlgrove.com/p/the-easiest-way-to-handle-errors), Ryan
Fleury gives concrete C examples, but the principles apply to other languages
as well. I haven't read Walter's book recommendation "A Philosophy of Software
Design" yet, and if you haven't either, maybe that post is more accessible.
Jul 03
next sibling parent reply "H. S. Teoh" <hsteoh qfbox.info> writes:
On Fri, Jul 03, 2026 at 11:08:26PM +0000, Dennis via Digitalmars-d wrote:
[...]
 In [The Easiest Way To Handle Errors Is To Not Have
 Them](https://www.dgtlgrove.com/p/the-easiest-way-to-handle-errors),
 Ryan Fleury gives concrete C examples, but the principles apply to
 other languages as well. I haven't read Walter's book recommendation
 "A Philosophy of Software Design" yet, and if you haven't either,
 maybe that post is more accessible.
That post is insightful indeed. Only, it begs the question: if zero initialization is so good, why don't we generalize it to structs with pointers too? The problem with null pointers is that we've been conditioned to treat it as a special value, a bad memory access, such that even the modern OS is designed to trigger an OS-level exception (i.e. a SEGV signal) when a process tries to read from address 0. But does it have to be this way? What if address 0 is a VALID address to read from, and always guaranteed to be zero? Make the first page (4K or however big you want it to be) of the program's address space point to a read-only zero page. The program is designed such that all types are zero-initialized. So, a pointer to address 0 is a pointer to the .init value of any type. So a null pointer always points to a valid zero-initialized instance of any type. Bingo! No more segfaults. No more special initialization of pointers. No need to allocate special memory to hold nil values of each type - page 0 of your address space *is* the nil value of your types. Most initialization code can be elided. Maybe I should start a new programming language on this basis: zero initialization is the only initialization, and all code must be written to expect zero as a valid empty value. // As far as D is concerned, defaulting floats to NaN breaks the pattern of zero-initialization, which sucks. We should default to 0.0 instead! T -- May you live all the days of your life. -- Jonathan Swift
Jul 03
next sibling parent reply Guillaume Piolat <first.name gmail.com> writes:
On Friday, 3 July 2026 at 23:57:05 UTC, H. S. Teoh wrote:
 As far as D is concerned, defaulting floats to NaN breaks the 
 pattern of zero-initialization, which sucks.  We should default 
 to 0.0 instead!
Are we going to read this suggestion over and over for the next years? Because it doesn't make a lot of difference.
Jul 04
parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 04/07/2026 9:51 PM, Guillaume Piolat wrote:
 On Friday, 3 July 2026 at 23:57:05 UTC, H. S. Teoh wrote:
 As far as D is concerned, defaulting floats to NaN breaks the pattern 
 of zero-initialization, which sucks.  We should default to 0.0 instead!
Are we going to read this suggestion over and over for the next years? Because it doesn't make a lot of difference.
Actually it does! But not for the reason people suggest. It decreases your binary size, and allows initialization to be done with memset instead of memcpy. So it can increase performance since you won't do reads during initialization.
Jul 04
next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 7/4/2026 3:02 AM, Richard (Rikki) Andrew Cattermole wrote:
 Actually it does!
If you want to initialize to 0: ```d float f = 0; float[100] a = 0; struct S { float s = 0; } S s; ```
Jul 04
parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 05/07/2026 6:56 AM, Walter Bright wrote:
 On 7/4/2026 3:02 AM, Richard (Rikki) Andrew Cattermole wrote:
 Actually it does!
If you want to initialize to 0: ```d float f = 0; float[100] a = 0; struct S { float s = 0; } S s; ```
This is not about being able to do it, nor is it an argument for the change itself. This is about people not knowing that there are benefits to changing.
Jul 04
prev sibling parent Adam D. Ruppe <destructionator gmail.com> writes:
On Saturday, 4 July 2026 at 10:02:46 UTC, Richard (Rikki) Andrew 
Cattermole wrote:
 It decreases your binary size, and allows initialization to be 
 done with memset instead of memcpy.

 So it can increase performance since you won't do reads during 
 initialization.
Yup, this is what tipped opend over to change all built in types to be zero initialized, including char and float. I think the nan arguments are actually pretty good, and this is a but of a runtime breaking change, but overall the zeroes have a preponderance of benefits.
Jul 04
prev sibling parent monkyyy <crazymonkyyy gmail.com> writes:
On Friday, 3 July 2026 at 23:57:05 UTC, H. S. Teoh wrote:
 
 What if address 0 is a VALID address to read from, and always 
 guaranteed to be zero?  Make the first page (4K or however big 
 you want it to be) of the program's address space point to a 
 read-only zero page.  The program is designed such that all 
 types are zero-initialized.  So, a pointer to address 0 is a 
 pointer to the .init value of any type.
```d import std; template innate(T,alias data,discrim...){ T innate=data; } struct unnullablepointer(T){ T* where; ref T get()=> where==null ? innate!(T,T.init) : *where; auto opOpAssign(string s:"&")(ref T t)=>where=&t; auto opAssign(T t)=>get=t; auto opEquals(T t)=>get==t; } unittest{ unnullablepointer!int foo; foo=3; assert(foo==3); int bar; foo&=bar; foo=5; assert(bar==5); } ``` quick, send this gist to adam 2
Jul 04
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 7/3/2026 4:08 PM, Dennis wrote:
 This is where [Walter is completely 
 right](https://forum.dlang.org/post/10utksm$1h2t$1 digitalmars.com) that the 
 best 'error handling' is *no handling*.
I thought I would never see this in the forum! I assumed that you were winding up to assert I was doing it all wrong! I am happy to be wrong in misjudging this, you made my day. Thanks, Dennis! The "no error handling" scheme of dealing with errors is a rather wrenching shifting of perspective, and at first blush it sounds all wrong, but I think it is going to become a big deal. I plan on talking about it at DConf.
Jul 03
next sibling parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Friday, July 3, 2026 6:48:36 PM Mountain Daylight Time Walter Bright via
Digitalmars-d wrote:
 The "no error handling" scheme of dealing with errors is a rather wrenching
 shifting of perspective, and at first blush it sounds all wrong, but I think it
 is going to become a big deal. I plan on talking about it at DConf.
I'm not sure that I'd say that it sounds all wrong so much as it's often non-obvious how or when you can do it. In many cases, it requires reframing the problem and/or looking at it differently, and that can be difficult. I'd say that it's similar to designing APIs which can't be misused. It's often impossible or impractical, but the closer that you can get to it, the fewer problems you (or anyone else) is likely to run into when using that API. But doing that is often not straightforward. - Jonathan M Davis
Jul 04
parent reply Walter Bright <newshound2 digitalmars.com> writes:
I agree it does not seem to be straightforward, as evidenced by the various 
perspectives on it in this thread.

I expect, however, that once it is tried a few times, this problem will abate.
Jul 05
parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 05/07/2026 8:14 PM, Walter Bright wrote:
 I agree it does not seem to be straightforward, as evidenced by the 
 various perspectives on it in this thread.
 
 I expect, however, that once it is tried a few times, this problem will 
 abate.
It already has as much as it will. Jonathan is correct its a hard thing to put into practice. I've watched over many years reviewers of dlang repositories to get people to simplify their code and eliminate such opportunities for problems. The reason code quality books like these exist is because what they talk about is hard and less than desirably understood. Or just outright opinion only. Educating people not familiar with this subject is what is currently missing.
Jul 05
parent reply monkyyy <crazymonkyyy gmail.com> writes:
On Sunday, 5 July 2026 at 08:25:55 UTC, Richard (Rikki) Andrew 
Cattermole wrote:
 
 The reason code quality books like these exist is because what 
 they talk about is hard and less than desirably understood. Or 
 just outright opinion only.
Uncle bob is functionally a con artist. Demand does not imply competence in the sellsmen, cocaine cough drops for children sold for decades presumably people wanted their children to be healthy, no one would pick a random decade in 1800s and blindly take their cures. Industry's that changes their mind every decade should be treated as such.
Jul 05
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 7/5/2026 4:48 AM, monkyyy wrote:
 Uncle bob is functionally a con artist.
Not sure what Bob Martin has to do with this.
Jul 05
parent monkyyy <crazymonkyyy gmail.com> writes:
On Sunday, 5 July 2026 at 19:42:28 UTC, Walter Bright wrote:
 On 7/5/2026 4:48 AM, monkyyy wrote:
 Uncle bob is functionally a con artist.
Not sure what Bob Martin has to do with this.
luckly for you I wrote this directly under that
 Demand does not imply competence in the sellsmen, cocaine 
 cough drops for children sold for decades presumably people 
 wanted their children to be healthy, no one would pick a 
 random decade in 1800s and blindly take their cures.
https://cdn.sanity.io/images/0vv8moc6/pharmacytimes/9b23dfcd0eb22c5eae0dab659e4f264f8072e3c5-525x322.jpg?fit=crop&auto=format Do you use cocaine for your tooth aches? Why care about the current decade's fad solutions?
Jul 05
prev sibling parent reply Dennis <dkorpel gmail.com> writes:
On Saturday, 4 July 2026 at 00:48:36 UTC, Walter Bright wrote:
 I thought I would never see this in the forum! I assumed that 
 you were winding up to assert I was doing it all wrong!

 I am happy to be wrong in misjudging this, you made my day.

 Thanks, Dennis!
Glad to hear that! I generally agree with your philosophies about simplicity, avoiding negations, avoiding overengineered templates, using parameters instead of globals etc. It's just the specific implementation of those ideas that sometimes lead to long discussions.
Jul 06
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 7/6/2026 6:12 AM, Dennis wrote:
 Glad to hear that! I generally agree with your philosophies about simplicity, 
 avoiding negations, avoiding overengineered templates, using parameters
instead 
 of globals etc. It's just the specific implementation of those ideas that 
 sometimes lead to long discussions.
I'm still learning the right way to do those things.
Jul 06
parent monkyyy <crazymonkyyy gmail.com> writes:
On Monday, 6 July 2026 at 22:18:23 UTC, Walter Bright wrote:
 I'm still learning the right way to do those things.
heres a helpful link for any future question you have: https://forum.dlang.org/group/learn
Jul 06
prev sibling next sibling parent =?UTF-8?Q?Ali_=C3=87ehreli?= <acehreli yahoo.com> writes:
On 7/3/26 4:08 PM, Dennis wrote:

 "Positive
 Conversion Overflow". Without attaching more information to Exceptions
 in intermediate catch blocks, the error is devoid of context.
Yes but I automated that for a C++ library.
 (Unless
 you print the stack trace but it's not like a regular user can make any
 sense of that)
I used a set of macros to print a stack of error messages.
 current trendy thing is returning `Result<Value, Error>` types
I don't know what Error is for so I returned e.g. ReturnValue<int>.
 like Rust does.
No, I did it my way. :D
 I don't have that much Rust experience, but I have seen code
 full of `result.expect("quick message")`
The ugliness that came with macros was ReturnValue<int> foo(int i) { TRY(bar(i) == 42 MSG("Failed to do 'bar' for %d", i)); return 100; } And bar(i) contains other TRY, etc. macros to stack error messages for context. What D intentionally lacks from C++: 1) Macros like TRY can inject statements like 'return failure;' D cannot (does not) do that. 2) TRY internally returns 'failure' upon failure, a value of a special type that implicitly converts to ReturnValue<T>. Similarly, 'return 100' above is an implicit conversion. D cannot (does not) do that. It worked very well for me. Ali
Jul 03
prev sibling next sibling parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Friday, July 3, 2026 5:57:05 PM Mountain Daylight Time H. S. Teoh via
Digitalmars-d wrote:
 As far as D is concerned, defaulting floats to NaN breaks the pattern of
 zero-initialization, which sucks.  We should default to 0.0 instead!
Well, types in general don't have zero initialization. It's just the integer types which do. And pointers too, I guess, since null is technically 0, but from D's perspective, that's an implementation detail. And the character types don't initialize to 0 even though they're numbers. And user-defined types often don't initialize to zero. So, I'd argue that there really isn't a consistent pattern of initializing to zero anyway. Types in general have a default value, and that value various wildly depending on the type. There are reasons why it could be argued that initializing floating point values to 0 would be better, but it really doesn't make the language more consistent. And from what I've seen, much as most of us absolutely hate the whole mess with NaNs, the guys who actually do a lot of math with their programs love it. And really, floating point values break all kinds of assumptions which types generally follow - like how NaN behaves with comparisons. It results in utter nonsense such as a == b and a != b having the same result, which most code assumes isn't a thing, because sane types do not work that way. So, pretty much any time that floating point types are involved, they break normal behavior and expectations to one degree or another. - Jonathan M Davis
Jul 04
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 7/4/2026 1:01 PM, Jonathan M Davis wrote:
 And from what I've seen, much as most of us absolutely hate the
 whole mess with NaNs, the guys who actually do a lot of math with their
 programs love it.
Here's a nice explanation of why NaNs are needed: https://pzarycki.com/en/posts/js-nan/ NaNs are your friend with computer floating point math. The whole mess would (and did) exist before NaN was invented and standardized.
Jul 04
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Saturday, July 4, 2026 3:20:04 PM Mountain Daylight Time Walter Bright via
Digitalmars-d wrote:
 On 7/4/2026 1:01 PM, Jonathan M Davis wrote:
 And from what I've seen, much as most of us absolutely hate the
 whole mess with NaNs, the guys who actually do a lot of math with their
 programs love it.
Here's a nice explanation of why NaNs are needed: https://pzarycki.com/en/posts/js-nan/ NaNs are your friend with computer floating point math. The whole mess would (and did) exist before NaN was invented and standardized.
I'm aware, but IMHO, the result is a disgusting mess. But honestly, I hate floating point values in general. Integers don't behave entirely like proper math, but they basically do outside of the fact that you have to worry about overflow and the fact that they have truncating division. So, you can mostly ignore the fact that they don't entirely behave like proper math, and they're pretty reasonable to work with. Floating points on the other hand get all kinds of screwy depending on the math you use, with the order of operations affecting precision and the like. Don did a good talk on them at dconf a number of years ago, explaining what to watch out for and how to deal with them, but if anything, it just further cemented my opinion that they should be avoided as much as possible. Sometimes, you don't have a choice, but if I do have a choice, I don't use them. If we had a fixed precision decimal type, then I'd likely use that instead, though that likely comes with its own problems. Of course, what would we'd ideally have would be types that behaved like actual math, but computers obviously have limitations which make that impossible. (particularly when you care about performance). And the fact that floating point values make it so that you can't require that operations such as the comparison operators behave reliably across all types just makes the situation that much worse. There are clearly good reasons for why those operations work the way that they do with floating point types, and we're pretty much stuck with them being that way, but it makes it so that you can't rely on those operations being consistent across types, which is bad for generic code in particular. So, I'm not proposing that D does anything different with floating point types, and if I were to create my own language, I'd probably implement them the same way that they are in D, because we really don't have a good alternative. But I _really_ don't like them. - Jonathan M Davis
Jul 04
next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
Of course. The issue is really that floating point is the best we can do when 
attempting to represent irrational numbers. We cannot even represent them on 
paper, other than as symbols like pi.

Before floating point, we had slide rules. They didn't have NaNs, but were only 
good for 3 digits. There is no way to accurately calculate an irrational number 
- not with pen and paper, not with slide rules, not with calculators, and not 
with computers. Not even a decimal type will slay that dragon.

But back to NaNs in particular.

Here's what sparked my interest in NaNs:

Back in the bad old C days,
```c
float f;
```
was uninitialized, i.e. would have a garbage value. This would often go 
unnoticed, silently corrupting results. At some point years later, a more 
advanced compiler would give: "Warning: use of uninitialized variable". The 
engineer who wrote it was long gone, and the sad sack maintainer had to fix it. 
So Mr. Sad Sack didn't know what the initialization should be, so "zero should 
be good enough for anyone" and 0.0 was added, and it compiled without error,
and 
so it was all good.

And never mind the computation it was supposed to generate was wrong, and the 
error possibly not detected.

D has a philosophy of "doing the wrong thing should be harder than doing the 
right thing." We see this in how things like void initializations work, you
have 
to do it deliberately.

So,
```d
float f;
```
is doing the wrong thing, and so it gets initialized by NaN to draw attention
to 
it being wrong. Then, we hope, it will be properly initialized by the engineer 
who writes the original code, not some junior maintainer who has no idea what 
the correct value should be and so just inserts 0.0 to shut the compiler up.

The bottom line is default 0.0 initialization hides bugs, and NaN exposes bugs. 
I prefer the latter.
Jul 04
parent reply "H. S. Teoh" <hsteoh qfbox.info> writes:
On Sat, Jul 04, 2026 at 04:55:22PM -0700, Walter Bright via Digitalmars-d wrote:
 Of course. The issue is really that floating point is the best we can
 do when attempting to represent irrational numbers. We cannot even
 represent them on paper, other than as symbols like pi.
Mostly, but not 100% true. Turns out, it *is* possible to represent certain classes of irrationals losslessly using only integers. There is a series of theorems in algebra to the effect that algebraic numbers of degree N can be represented by a N-dimensional vector of rationals (or equivalently, an (2N)-dimensional vector of integers), closed under field operations. Thus, it is actually possible to perform (exact!) arithmetic involving algebraic numbers using only integer arithmetic. The caveat is that the coefficients of these vectors may be very large -- in general, when multiplying or dividing algebraic numbers, the coefficients may grow in number of digits by up to |x|*N, where |x| is the number of digits of the corresponding coefficient in the operands. If fixed sized integer coefficients are used, this quickly leads to integer overflow. So for practical applications this scheme is feasible only for small N. In particular, it works quite well for N=2, i.e., for numbers of the form (x + y*√r) for rational x, y and fixed r. (When r is not fixed, multiplying two numbers may increase N to 4, with the accompanying issues with integer overflow.) I have a proof of concept here: https://github.com/quickfur/qrat With this little library, I can perform exact arithmetic of numbers of the form (x + y*√r). I have used this for non-trivial computations, e.g., to compute exact coordinates for 4-dimensional polytopes in the field Q(√5). So it's not entirely true that floating point is inevitable when dealing with irrationals. If you only need to deal with irrationals of the above form, it's entirely possible to completely avoid floating-point, and get exact results for your computations. The QRat library can also be used with BigInt coefficients, which completely avoid the integer overflow problem. In principle, you can perform exact arithmetic with algebraic numbers of degree N using BigInt coefficients, without ever touching floating-point at all! [...]
 The bottom line is default 0.0 initialization hides bugs, and NaN
 exposes bugs. I prefer the latter.
Your argument only applies to *modifying* existing *C* code that fails to initialize float variables and assuming 0.0 is valid initialization. For new code, written in D which initializes by default, your argument doesn't apply. New code is written to work with whatever default value the language imposes on the type, so whether it's NaN or 0.0 doesn't actually change anything. For code ported from C, yes defaulting to NaN will catch bugs. But if you're porting from C, you shouldn't be copy-n-pasting code blindly without review to begin with. Code that performs integer calculation also suffers from the same problem if you fail to initial your integer to the right value, and got 0 instead. You might as well say we should initialize ints to -INTMAX so that if your result suddenly comes out as an unusually large number where you expect a small one, you'll know if you have a bug. T -- Creativity is not an excuse for sloppiness.
Jul 04
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 7/4/2026 5:29 PM, H. S. Teoh wrote:
 On Sat, Jul 04, 2026 at 04:55:22PM -0700, Walter Bright via Digitalmars-d
wrote:
 Of course. The issue is really that floating point is the best we can
 do when attempting to represent irrational numbers. We cannot even
 represent them on paper, other than as symbols like pi.
Mostly, but not 100% true. [...]
I didn't know that! Nice read. Thank you for posting it.
 The bottom line is default 0.0 initialization hides bugs, and NaN
 exposes bugs. I prefer the latter.
Your argument only applies to *modifying* existing *C* code that fails to initialize float variables and assuming 0.0 is valid initialization. For new code, written in D which initializes by default, your argument doesn't apply. New code is written to work with whatever default value the language imposes on the type, so whether it's NaN or 0.0 doesn't actually change anything.
It still applies. People will still write: ```d float f; ``` and forget to initialize it. If it defaults to 0, and 0 is not correct for the need, then a subtle bug is introduced. If it defaults to NaN, it will (eventually) force the programmer to think about what the actual initial value should be. That is the goal of this.
 For code ported from C, yes defaulting to NaN will catch bugs.  But if
 you're porting from C, you shouldn't be copy-n-pasting code blindly
 without review to begin with.
When you're converting boatloads of C to D, reviewing every line is not going to happen. What people should do and what they actually do can be very different. D is designed, as I pointed out earlier, to make it easier to do the right thing than the wrong thing.
 Code that performs integer calculation also suffers from the same
 problem if you fail to initial your integer to the right value, and got
 0 instead.
I would have used int.NaN if there was a NaN value for integers.
 You might as well say we should initialize ints to -INTMAX
 so that if your result suddenly comes out as an unusually large number
 where you expect a small one, you'll know if you have a bug.
Indeed, with int.min you may notice something is wrong, but with NaN you are going to notice something is wrong.
Jul 05
parent reply Alexandru Ermicioi <alexandru.ermicioi gmail.com> writes:
On Sunday, 5 July 2026 at 08:11:31 UTC, Walter Bright wrote:
 It still applies. People will still write:
 ```d
 float f;
 ```
Maybe D should drop default initialization completely? Then this issue will disappear.
Jul 05
next sibling parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 05/07/2026 8:26 PM, Alexandru Ermicioi wrote:
 On Sunday, 5 July 2026 at 08:11:31 UTC, Walter Bright wrote:
 It still applies. People will still write:
 ```d
 float f;
 ```
Maybe D should drop default initialization completely? Then this issue will disappear.
Absolutely not. It doesn't just affect variables in a function, it also affects fields, globals; all allocations. It makes all variables uninitialized, which opens the door to a significant number of CVE's that C has. Walter got this right, making the default type state initialized. What we are able to do here though is throw static analysis at the problem: https://github.com/dlang/dmd/blob/master/changelog/dmd.fastdfa.uninitialized.dd
Jul 05
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 7/5/2026 1:37 AM, Richard (Rikki) Andrew Cattermole wrote:
 What we are able to do here though is throw static analysis at the problem: 
 https://github.com/dlang/dmd/blob/master/changelog/dmd.fastdfa.uninitialized.dd
Static analysis cannot solve the problem 100%. It's the ole' halting problem.
Jul 05
next sibling parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 06/07/2026 7:08 AM, Walter Bright wrote:
 On 7/5/2026 1:37 AM, Richard (Rikki) Andrew Cattermole wrote:
 What we are able to do here though is throw static analysis at the 
 problem: https://github.com/dlang/dmd/blob/master/changelog/ 
 dmd.fastdfa.uninitialized.dd
Static analysis cannot solve the problem 100%. It's the ole' halting problem.
20 years ago yeah. But today it is considered solved in the sense that we do have static analyzers that are sound that catch it. https://www.nist.gov/publications/sate-vi-report-bug-injection-and-collection See 6.2.4.7 and 6.4.3. "Both Astrée and Frama-C with Eva satisfied the SATE VI Ockham Sound Analysis Criteria." ----------- However to solve this in D fully, isn't that hard, and we can make some minor sacrifices in false positives to make it a good experience. Due to D being based upon the type state initialized, to downgrade to uninitialized, you must opt-out of it and into uninitialized. In other words annotate. This removes inter-procedural analysis as a requirement. By eliminating uninitialized into global, and field, you can remove whole program analysis. By using separation logic (paper published in 2002 hintity hint), you can model the indirection and fields. https://en.wikipedia.org/wiki/Separation_logic Throw in chaotic iteration, easy peasy to solve. Of course I'm not saying that we need to solve it, due to there being some time cost involved, but its not the halting problem!
Jul 05
parent reply monkyyy <crazymonkyyy gmail.com> writes:
On Monday, 6 July 2026 at 02:21:12 UTC, Richard (Rikki) Andrew 
Cattermole wrote:
 On 06/07/2026 7:08 AM, Walter Bright wrote:
 Static analysis cannot solve the problem 100%. It's the ole' 
 halting problem.
20 years ago yeah. But today it is considered solved in the sense that we do have static analyzers that are sound that catch it.
Then they are wrong; you just chasing more and more complex approximations with stranger and stranger rules or ignoring more and more edge cases.
Jul 05
parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 06/07/2026 3:41 PM, monkyyy wrote:
 On Monday, 6 July 2026 at 02:21:12 UTC, Richard (Rikki) Andrew 
 Cattermole wrote:
 On 06/07/2026 7:08 AM, Walter Bright wrote:
 Static analysis cannot solve the problem 100%. It's the ole' halting 
 problem.
20 years ago yeah. But today it is considered solved in the sense that we do have static analyzers that are sound that catch it.
Then they are wrong; you just chasing more and more complex approximations with stranger and stranger rules or ignoring more and more edge cases.
Then you have not understood anything that I wrote in that post.
Jul 05
prev sibling parent reply Araq <rumpf_a web.de> writes:
On Sunday, 5 July 2026 at 19:08:44 UTC, Walter Bright wrote:
 On 7/5/2026 1:37 AM, Richard (Rikki) Andrew Cattermole wrote:
 What we are able to do here though is throw static analysis at 
 the problem: 
 https://github.com/dlang/dmd/blob/master/changelog/dmd.fastdfa.uninitialized.dd
Static analysis cannot solve the problem 100%. It's the ole' halting problem.
No, it's just you not understanding the halting problem. In reality Java solved this problem since its inception. Many other languages followed since then.
Jul 30
parent reply monkyyy <crazymonkyyy gmail.com> writes:
On Thursday, 30 July 2026 at 07:30:54 UTC, Araq wrote:
 On Sunday, 5 July 2026 at 19:08:44 UTC, Walter Bright wrote:
 On 7/5/2026 1:37 AM, Richard (Rikki) Andrew Cattermole wrote:
 What we are able to do here though is throw static analysis 
 at the problem: 
 https://github.com/dlang/dmd/blob/master/changelog/dmd.fastdfa.uninitialized.dd
Static analysis cannot solve the problem 100%. It's the ole' halting problem.
No, it's just you not understanding the halting problem. In reality Java solved this problem since its inception. Many other languages followed since then.
Walters correct here, you either ban things or have holes. Java doesnt have void*, so they havnt solved the problem for the types of things youd use a void* for in c. Suppose I have a ubyte and a void* and a list of types for a tagged reference, I use 0 to mean void itself, 1 for int etc. then N+1 I use to mean slices of my sumtype and there will be an int that the length of the slice at index 0. Then N+M is a slice of slice of... of my sumtype, till you fill up the ubyte. Its valid, good luck convincing an opinionated static checker to allow it.
Jul 30
parent reply "H. S. Teoh" <hsteoh qfbox.info> writes:
On Thu, Jul 30, 2026 at 03:20:48PM +0000, monkyyy via Digitalmars-d wrote:
 On Thursday, 30 July 2026 at 07:30:54 UTC, Araq wrote:
 On Sunday, 5 July 2026 at 19:08:44 UTC, Walter Bright wrote:
 On 7/5/2026 1:37 AM, Richard (Rikki) Andrew Cattermole wrote:
 What we are able to do here though is throw static analysis at
 the problem: https://github.com/dlang/dmd/blob/master/changelog/dmd.fastdfa.uninitialized.dd
Static analysis cannot solve the problem 100%. It's the ole' halting problem.
No, it's just you not understanding the halting problem. In reality Java solved this problem since its inception. Many other languages followed since then.
Walters correct here, you either ban things or have holes. Java doesnt have void*, so they havnt solved the problem for the types of things youd use a void* for in c. Suppose I have a ubyte and a void* and a list of types for a tagged reference, I use 0 to mean void itself, 1 for int etc. then N+1 I use to mean slices of my sumtype and there will be an int that the length of the slice at index 0. Then N+M is a slice of slice of... of my sumtype, till you fill up the ubyte. Its valid, good luck convincing an opinionated static checker to allow it.
That's called a tagged union. An old, well-known idiom. Pretty sure static checkers can be taught to recognize it. T -- Eat more doughnuts. It's the original hole food.
Jul 30
parent monkyyy <crazymonkyyy gmail.com> writes:
On Thursday, 30 July 2026 at 15:58:04 UTC, H. S. Teoh wrote:
 On Thu, Jul 30, 2026 at 03:20:48PM +0000, monkyyy via 
 Digitalmars-d wrote:
 On Thursday, 30 July 2026 at 07:30:54 UTC, Araq wrote:
 On Sunday, 5 July 2026 at 19:08:44 UTC, Walter Bright wrote:
 On 7/5/2026 1:37 AM, Richard (Rikki) Andrew Cattermole 
 wrote:
 What we are able to do here though is throw static 
 analysis at
 the problem: 
 https://github.com/dlang/dmd/blob/master/changelog/dmd.fastdfa.uninitialized.dd
Static analysis cannot solve the problem 100%. It's the ole' halting problem.
No, it's just you not understanding the halting problem. In reality Java solved this problem since its inception. Many other languages followed since then.
Walters correct here, you either ban things or have holes. Java doesnt have void*, so they havnt solved the problem for the types of things youd use a void* for in c. Suppose I have a ubyte and a void* and a list of types for a tagged reference, I use 0 to mean void itself, 1 for int etc. then N+1 I use to mean slices of my sumtype and there will be an int that the length of the slice at index 0. Then N+M is a slice of slice of... of my sumtype, till you fill up the ubyte. Its valid, good luck convincing an opinionated static checker to allow it.
That's called a tagged union. An old, well-known idiom. Pretty sure static checkers can be taught to recognize it. T
You stopped reading 2 sentences into that paragraph: https://www.kimi.com/share/19fb3d16-cd62-8e62-8000-0000ae040a09 You can append special cases to a formal system, I believe "this statement is false" is tralse, so you could engineer the entire system to allow this case algebraically solve the rules of what every operation is, enumerate your new list of rules, have your new compiler. "This statement is tralse", `tralse^2`? whats `tralse^7 || tralse^3` equal to? Ops, can your new compiler keep up? Appending special cases does not even remotely keep up with new expressible hostile cases in formal systems; anyone who says otherwise still hasn't internalized the 100 year old result. Its mountains of work to handle a slight growth in type theory in a compiler, its a line of code to squeeze it harder.
Jul 30
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 7/5/2026 1:26 AM, Alexandru Ermicioi wrote:
 Maybe D should drop default initialization completely?
That's how C works. It's a bug fountain.
Jul 05
next sibling parent Alexandru Ermicioi <alexandru.ermicioi gmail.com> writes:
On Sunday, 5 July 2026 at 19:05:12 UTC, Walter Bright wrote:
 On 7/5/2026 1:26 AM, Alexandru Ermicioi wrote:
 Maybe D should drop default initialization completely?
That's how C works. It's a bug fountain.
I.e. force initialisation to be done before use, ofc.
Jul 06
prev sibling parent reply Lars Johansson <lasse 11dim.se> writes:
On Sunday, 5 July 2026 at 19:05:12 UTC, Walter Bright wrote:
 On 7/5/2026 1:26 AM, Alexandru Ermicioi wrote:
 Maybe D should drop default initialization completely?
That's how C works. It's a bug fountain.
A humble question. Is default initialization relevant with modern editors and AI?
Jul 06
parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 07/07/2026 12:31 AM, Lars Johansson wrote:
 On Sunday, 5 July 2026 at 19:05:12 UTC, Walter Bright wrote:
 On 7/5/2026 1:26 AM, Alexandru Ermicioi wrote:
 Maybe D should drop default initialization completely?
That's how C works. It's a bug fountain.
A humble question. Is default initialization relevant with modern editors and AI?
How much of the training data that LLM's learn from do you think have not been properly initialized? A large percentage of the C test files for ImportC that dates back over 40 years, is not initialized. There are reads on unitialized variables EVERYWHERE. As for modern editors, the best case scenario is that they run a static analyzer to catch such cases. So for D, don't expect that to happen. Not even Walter uses an IDE. He has his own.
Jul 06
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 7/6/2026 5:38 AM, Richard (Rikki) Andrew Cattermole wrote:
 A large percentage of the C test files for ImportC that dates back over 40 
 years, is not initialized. There are reads on unitialized variables EVERYWHERE.
Secret features of ImportC: * default initializes variables! * allows forward references!! * does CTFE on constant expressions!!! ImportC implements a better C language than C.
Jul 08
parent libxmoc <libxmoc gmail.com> writes:
On Wednesday, 8 July 2026 at 18:52:44 UTC, Walter Bright wrote:
 On 7/6/2026 5:38 AM, Richard (Rikki) Andrew Cattermole wrote:
 A large percentage of the C test files for ImportC that dates 
 back over 40 years, is not initialized. There are reads on 
 unitialized variables EVERYWHERE.
Secret features of ImportC: * default initializes variables! * allows forward references!! * does CTFE on constant expressions!!! ImportC implements a better C language than C.
Agreed. I have been using D as a better C language. It's been great so far!
Jul 08
prev sibling parent reply Zz <zz zz.com> writes:
On Saturday, 4 July 2026 at 22:41:21 UTC, Jonathan M Davis wrote:
 So, I'm not proposing that D does anything different with 
 floating point types, and if I were to create my own language, 
 I'd probably implement them the same way that they are in D, 
 because we really don't have a good alternative. But I _really_ 
 don't like them.

 - Jonathan M Davis
Have you looked at this. https://zylinski.se/posts/a-programming-language-for-me/#zero-is-initialized-zii Zz
Jul 05
parent Kapendev <alexandroskapretsos gmail.com> writes:
On Sunday, 5 July 2026 at 07:53:15 UTC, Zz wrote:
 On Saturday, 4 July 2026 at 22:41:21 UTC, Jonathan M Davis 
 wrote:
 So, I'm not proposing that D does anything different with 
 floating point types, and if I were to create my own language, 
 I'd probably implement them the same way that they are in D, 
 because we really don't have a good alternative. But I 
 _really_ don't like them.

 - Jonathan M Davis
Have you looked at this. https://zylinski.se/posts/a-programming-language-for-me/#zero-is-initialized-zii Zz
It's a good first option when making a new type. Doesn't work for types like this: ```d struct DrawOptions { Vec2 origin = Vec2(0.0f); Vec2 scale = Vec2(1.0f); // <-- 1 float rotation = 0.0f; Rgba color = white; // <-- 2 Hook hook = Hook.topLeft; Flip flip = Flip.none; ubyte layer = 0; } ```
Jul 05
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 7/3/2026 4:08 PM, Dennis wrote:
 In [The Easiest Way To Handle Errors Is To Not Have 
 Them](https://www.dgtlgrove.com/p/the-easiest-way-to-handle-errors), Ryan
Fleury 
 gives concrete C examples, but the principles apply to other languages as
well. 
 I haven't read Walter's book recommendation "A Philosophy of Software Design" 
 yet, and if you haven't either, maybe that post is more accessible.
Fleury makes some very sensible points. Well worth reading!
Jul 05
parent reply Meta <jared771 gmail.com> writes:
On Sunday, 5 July 2026 at 20:16:24 UTC, Walter Bright wrote:
 On 7/3/2026 4:08 PM, Dennis wrote:
 In [The Easiest Way To Handle Errors Is To Not Have 
 Them](https://www.dgtlgrove.com/p/the-easiest-way-to-handle-errors), Ryan
Fleury gives concrete C examples, but the principles apply to other languages
as well. I haven't read Walter's book recommendation "A Philosophy of Software
Design" yet, and if you haven't either, maybe that post is more accessible.
Fleury makes some very sensible points. Well worth reading!
"...return a pointer to a “nil struct”, rather than a null pointer" "Aside from a number of silly implementation details of errno, there are reasonable aspects of its design." "The fact of the matter is, the larger the number of types, the larger the number of required codepaths." "A helpful lesson for me was in reframing error information returned by a codepath as error information in addition to whatever the “non-error result” is. This small change eliminates needless bifurcation of the code receiving the result—it can simply be one codepath which processes both valid results (or gracefully no-ops, if the valid results are zero-initialized), and any error information." "...in my view, the fact that there is such a widescale (and often passionate) conversation about the “need” for “error handling language features” is indicative enough of the embarrassing state of software development." This guy's mindset insane to me. Rather than have his program crash when it tries to dereference a null pointer, he wants to paper over it and keep going like nothing's wrong. A segfault or an assert triggering is the correct behaviour in this case; it's indicative that something has gone catastrophically wrong, such that execution cannot continue. What if he wrote a text editing program that uses this philosophy? When the user wants to save their work, and fopen returns such a "nil struct" instead of a null pointer, it would appear to the user that their file was saved to disk, only for it to be completely lost once the program exits. Also, who unironically thinks errno is a good way to handle errors in 2023? Some of these C programmers need to accept that programming techniques have advanced since the 70s. That being said, his "valid zero values" and "fail fast" points are fine, but even when making a good point, he trips over his bad ones: "One of the major exceptions to zero initialization as a rule is that it’s sometimes worthwhile to compromise it for the purpose of providing nil struct pointers." I dunno, he reminds me of a guy I used to work with who wrote maddeningly buggy code. I'd implement some feature or fix a bug in one part of the code, but unit tests for a completely different part of the code (which he wrote) would fail in the CI. I'd have to spend hours tracing through his code to figure out where an assumption he made about the state of the program was silently failing, because he refused to handle errors in a reasonable way and instead wrote code that just ignored them and kept going.
Jul 05
next sibling parent Kapendev <alexandroskapretsos gmail.com> writes:
On Sunday, 5 July 2026 at 21:57:55 UTC, Meta wrote:
 Also, who unironically thinks errno is a good way to handle 
 errors in 2023? Some of these C programmers need to accept that 
 programming techniques have advanced since the 70s.
Errno itself is bad because it's too generic. If you make your own version of it and limit its scope to a specific system it can work OK. I'm not saying it's better than returning an error value :)
 fake null pointers
I never used the fake null pointer trick, so no idea.
Jul 05
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
Another term that describes his proposal is a "black hole object", which
accepts 
all input but does nothing.

The main thrust of his argument is eliminating the `if` statements that check 
for error states.
Jul 05
parent reply Meta <jared771 gmail.com> writes:
On Sunday, 5 July 2026 at 23:36:59 UTC, Walter Bright wrote:
 Another term that describes his proposal is a "black hole 
 object", which accepts all input but does nothing.

 The main thrust of his argument is eliminating the `if` 
 statements that check for error states.
Ya... I don't agree with him at all on that. His approach to reducing paths dedicated to error handling in his code is to ignore those errors - which is what his "nil struct" solution does. I don't think it's a good design at all. One thing that WOULD genuinely help reduce errors is leveraging type system invariants to make certain invalid states impossible - a great language-level solution is not having null pointers - but he is explicitly against that in this article.
Jul 05
parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 06/07/2026 3:31 PM, Meta wrote:
 On Sunday, 5 July 2026 at 23:36:59 UTC, Walter Bright wrote:
 Another term that describes his proposal is a "black hole object", 
 which accepts all input but does nothing.

 The main thrust of his argument is eliminating the `if` statements 
 that check for error states.
Ya... I don't agree with him at all on that. His approach to reducing paths dedicated to error handling in his code is to ignore those errors - which is what his "nil struct" solution does. I don't think it's a good design at all. One thing that WOULD genuinely help reduce errors is leveraging type system invariants to make certain invalid states impossible - a great language-level solution is not having null pointers - but he is explicitly against that in this article.
A major concern with this approach is you can end up doing work, and making a value appear to have valid data, but is actually invalid. Now you have program corruption and that is a far worse scenario than just ya know doing literally anything else.
Jul 05
prev sibling next sibling parent reply Dennis <dkorpel gmail.com> writes:
On Sunday, 5 July 2026 at 21:57:55 UTC, Meta wrote:
 Rather than have his program crash when it tries to dereference 
 a null pointer, he wants to paper over it and keep going like 
 nothing's wrong. A segfault or an assert triggering is the 
 correct behaviour in this case;
I have to admit that I find his example of a nil-struct a bit abstract. But to me, the takeaway is not "replace null checks with nil structs and pretend everything is fine now". The idea is that you rethink your design so the whole concept of `null` wasn't there in the first place. In math, the product of an empty set is defined as 1. This is a bit jarring at first, but in practice it works out beautifully in formulas and prevents discrete cases. A programmer might be tempted to implement `throw new Exception("at least 1 number expected as input for product()")` and impose unnecessary branches for 'error handling', when there was never a need for an 'error' to begin with. In Java, an ArrayList can be null, forcing null checks like: ```Java if (xyzList != null && xyzList.size() > 0) doSomething(); ``` https://stackoverflow.com/questions/29784314/can-an-arraylist-be-null-and-have-a-size-0 In D, you can always check `array.length`. It's designed so that whenever `array.ptr` is null, `array.length` is also 0 (save for ` system` shenanigans), defining the null error out of existence even when the variable is 0-initialized. I love that design!
 What if he wrote a text editing program that uses this 
 philosophy? When the user wants to save their work, and fopen 
 returns such a "nil struct" instead of a null pointer, it would 
 appear to the user that their file was saved to disk, only for 
 it to be completely lost once the program exits.
Defining errors out of existence is a hard sell, because it's not a single pattern that you universally apply. It's a case-by-case thing that requires real context and critical evaluation of your entire stack. This is indeed a case where plainly applying the 'nil struct' pattern is a bad idea. Instead, you could question why your application is 'opening a file' in the first place and why it would have the responsibility to error check. What if perhaps, your application would hand off the 'save data' to the OS/browser/ui framework/whatever, and make it their responsibility to write it to disk and report errors back to the user? Notice how in this shell command, it's zsh reporting the error: ``` echo "hello" > dummy/dummy.txt zsh: no such file or directory: dummy/dummy.txt ``` echo simply writes to stdout without doing fopen itself, so it doesn't have to do this error check. I know this doesn't define the error literally out of existence, it just makes it someone else's problem. But you can imagine if you have 100 shell programs like this, that's now 99 less error checks. As an aside, notice how when you `fopen("chain/of/directories/file.txt")`, you get to check 1 error. But behind the string API, conceptually you are doing `get("chain").get("of").get("directories").get("file.txt")`. Each of these can fail, so you could also handle 4 different error branches. Now compare this snippet from the article: ```C Node *n1 = ChildFromValue(root, 1); Node *n2 = ChildFromValue(n1, 2); Node *n3 = ChildFromValue(n2, 3); Node *n4 = ChildFromValue(n3, 4); ``` Do you see the resemblance? 🙂
 Also, who unironically thinks errno is a good way to handle 
 errors in 2023?
He said "there are reasonable aspects of its design". It being a global variable and only a single integer of information is certainly crappy. But I personally do agree with the principle that actions should prefer producing result data AND error data instead of result data OR error data. I personally implement this by returning the result, and putting error data in an `ErrorSink` parameter.
Jul 06
parent Walter Bright <newshound2 digitalmars.com> writes:
On 7/6/2026 5:00 AM, Dennis wrote:
 In D, you can always check `array.length`. It's designed so that whenever 
 `array.ptr` is null, `array.length` is also 0 (save for ` system`
shenanigans), 
 defining the null error out of existence even when the variable is 
 0-initialized. I love that design!
Yes, that worked out nicely!
 What if he wrote a text editing program that uses this philosophy? When the 
 user wants to save their work, and fopen returns such a "nil struct" instead 
 of a null pointer, it would appear to the user that their file was saved to 
 disk, only for it to be completely lost once the program exits.
One solution is for the nil struct to log errors internally, then at an appropriate time the errors can be examined and transmitted to the user.
 Defining errors out of existence is a hard sell, because it's not a single 
 pattern that you universally apply. It's a case-by-case thing that requires
real 
 context and critical evaluation of your entire stack.
There may be a general way of doing it, but I am not experienced enough in it to see it. I agree it's a case by case thing. In the D compiler, the error message printer keeps a sticky flag that there were errors. It also uses "error nodes" (like the nil struct) that enable the compiler to continue gracefully. The error recovery of the compiler got a great deal better when that was adopted. (The earlier scheme was an attempt to "repair" the bad input, which never worked very well.) In any case, it's a fun thing to think about with your own code.
Jul 08
prev sibling parent reply "H. S. Teoh" <hsteoh qfbox.info> writes:
On Sun, Jul 05, 2026 at 09:57:55PM +0000, Meta via Digitalmars-d wrote:
[...]
 On 7/3/2026 4:08 PM, Dennis wrote:
 In [The Easiest Way To Handle Errors Is To Not Have
 Them](https://www.dgtlgrove.com/p/the-easiest-way-to-handle-errors),
 Ryan Fleury gives concrete C examples, but the principles apply to
 other languages as well. I haven't read Walter's book
 recommendation "A Philosophy of Software Design" yet, and if you
 haven't either, maybe that post is more accessible.
[[...]
 This guy's mindset insane to me. Rather than have his program crash
 when it tries to dereference a null pointer, he wants to paper over it
 and keep going like nothing's wrong. A segfault or an assert
 triggering is the correct behaviour in this case; it's indicative that
 something has gone catastrophically wrong, such that execution cannot
 continue.
[...] Then you're missing his point. If you skim over his article, it's easy to pick up the part about using nil structs or "fake" pointers, but miss the other, equally important, part about writing your code in such a way that *it's still correct when passed a nil value*. Without this second part, you immediately run into all the problems that you mention. The whole point isn't only to substitute error paths with valid (but nil) values; it is to structure your code so that it handles both cases without bifurcating the code path. For example, if your function receives a buffer, then you could either have it take a pointer (the typical C approach), or an object that encodes the length of the buffer. In the first case, if the caller fails to allocate the buffer, you'd pass a null pointer to indicate the buffer doesn't exist. However, doing that means you need a null check. Instead, you could use the second approach: pass an object of zero length. Then write the function such that a zero-length buffer results in a no-op. Then when the caller fails to allocate the buffer, the function does nothing (harmful), without needing a null check. Note that you cannot ignore the second part -- if the function wasn't written to gracefully handle an empty buffer, it might do something totally wrong instead, like write to a dangling pointer (I see this a lot in the C code that I work with: even though a function may receive a buffer with length, the code was written with the implicit assumption that the length is non-zero, so when you pass in a zero length it malfunctions and does something stupid). // Now, you mention that in some cases errors should not be ignored, e.g. when you save a file and the operation failed. Obviously, you don't want to just silently ignore the error in that case. The conventional approach is to throw an exception. The problem with that is that it bifurcates the control flow, and most of all, these error paths are likely never tested. (Tell me, when was the last time you wrote a unittest to check that failing to open a file is handled correctly? Or when the disk is full and you try to write to a file?) Furthermore, these exceptional conditions often occur deep inside the call stack, at some low-level utility function that simply does not have the adequate context to know what to do with the error. So the only sane thing to do is to pass the error state back up the stack and let some caller higher up the call stack figure out what to do. Throwing an exception is typically one way of doing this. However, then you run into the problem of how a high-level function knows how to do: because it may be so distant it has no idea that this low-level function was even called, much less what kinds of exceptions it might throw. For example, you could have an I/O error in a buffered write utility function. It throws an exception -- but the caller is an XML generator that's part of some library. It also doesn't know what to do with the exception, other than propagate it, or return some error that XML write failed. So the error is pushed further up -- but the next caller is a document writer module (XML is only a small part of the document), which also doesn't know what to do, because it's called by a function trying to save a backup file. So it has to pass the error along as well. Then it turns out that backup function is called by a script parser that's trying to save a previous state before overwriting it with a new one. And the script parser is called by a macro utility in some spreadsheet application. Now consider the top level function, which is a user-action handler processing a user command to edit a spreadsheet cell. It has no idea that calling updateCell() can eventually call a buffered I/O function that throws an IOError -- so it doesn't even know to catch an IOError. And when the exception occurs, how is it supposed to know what to do? The best scenario at this point is to for it to display some generic error message that editing the cell failed. How is the user supposed to understand why such an apparently simple action as inputting a new value to a cell failed? // The proposed solution in the article is to keep a global error log instead of throwing an exception. So the I/O write function would return a nil object (remember, we're assuming that all the code, including its callers, are written such that the nil object is handled correctly), but in addition, write to a global error log. At some point in the call stack, presumably in the user-action handler, you'd want to know whether the operation succeeded or not. So that's where you'd check whether the error log is empty -- if not, now you have a log of what went wrong (I/O error -> XML write failed -> document save failed -> backup state failed -> script failed -> execute macro failed -> update cell failed), which can help the user understand why the operation failed. Yes, you DO have to eventually check for errors -- but now you can check for it only in a few places: in the actual low-level function that failed and in the top-level function handling user actions, instead of every level down the call stack (which leads to 2^N bifurcating code paths). T -- Жил-был король когда-то, при нём блоха жила.
Jul 06
next sibling parent Meta <jared771 gmail.com> writes:
On Monday, 6 July 2026 at 15:03:29 UTC, H. S. Teoh wrote:
 On Sun, Jul 05, 2026 at 09:57:55PM +0000, Meta via 
 Digitalmars-d wrote: [...]
 On 7/3/2026 4:08 PM, Dennis wrote:
 In [The Easiest Way To Handle Errors Is To Not Have 
 Them](https://www.dgtlgrove.com/p/the-easiest-way-to-handle-errors), Ryan
Fleury gives concrete C examples, but the principles apply to other languages
as well. I haven't read Walter's book recommendation "A Philosophy of Software
Design" yet, and if you haven't either, maybe that post is more accessible.
[[...]
 This guy's mindset insane to me. Rather than have his program 
 crash when it tries to dereference a null pointer, he wants to 
 paper over it and keep going like nothing's wrong. A segfault 
 or an assert triggering is the correct behaviour in this case; 
 it's indicative that something has gone catastrophically 
 wrong, such that execution cannot continue.
[...] Then you're missing his point. If you skim over his article, it's easy to pick up the part about using nil structs or "fake" pointers, but miss the other, equally important, part about writing your code in such a way that *it's still correct when passed a nil value*. Without this second part, you immediately run into all the problems that you mention.
I see your insinuation, but I read the article multiple times before posting my original message.
Jul 08
prev sibling parent reply ABrightLight <example example.com> writes:
On Monday, 6 July 2026 at 15:03:29 UTC, H. S. Teoh wrote:
 On Sun, Jul 05, 2026 at 09:57:55PM +0000, Meta via 
 Digitalmars-d wrote: [...]
 On 7/3/2026 4:08 PM, Dennis wrote:
 In [The Easiest Way To Handle Errors Is To Not Have 
 Them](https://www.dgtlgrove.com/p/the-easiest-way-to-handle-errors), Ryan
Fleury gives concrete C examples, but the principles apply to other languages
as well. I haven't read Walter's book recommendation "A Philosophy of Software
Design" yet, and if you haven't either, maybe that post is more accessible.
[[...]
 This guy's mindset insane to me. Rather than have his program 
 crash when it tries to dereference a null pointer, he wants to 
 paper over it and keep going like nothing's wrong. A segfault 
 or an assert triggering is the correct behaviour in this case; 
 it's indicative that something has gone catastrophically 
 wrong, such that execution cannot continue.
[...] Then you're missing his point. If you skim over his article, it's easy to pick up the part about using nil structs or "fake" pointers, but miss the other, equally important, part about writing your code in such a way that *it's still correct when passed a nil value*. Without this second part, you immediately run into all the problems that you mention. The whole point isn't only to substitute error paths with valid (but nil) values; it is to structure your code so that it handles both cases without bifurcating the code path. For example, if your function receives a buffer, then you could either have it take a pointer (the typical C approach), or an object that encodes the length of the buffer. In the first case, if the caller fails to allocate the buffer, you'd pass a null pointer to indicate the buffer doesn't exist. However, doing that means you need a null check. Instead, you could use the second approach: pass an object of zero length. Then write the function such that a zero-length buffer results in a no-op. Then when the caller fails to allocate the buffer, the function does nothing (harmful), without needing a null check. Note that you cannot ignore the second part -- if the function wasn't written to gracefully handle an empty buffer, it might do something totally wrong instead, like write to a dangling pointer (I see this a lot in the C code that I work with: even though a function may receive a buffer with length, the code was written with the implicit assumption that the length is non-zero, so when you pass in a zero length it malfunctions and does something stupid). // Now, you mention that in some cases errors should not be ignored, e.g. when you save a file and the operation failed. Obviously, you don't want to just silently ignore the error in that case. The conventional approach is to throw an exception. The problem with that is that it bifurcates the control flow, and most of all, these error paths are likely never tested. (Tell me, when was the last time you wrote a unittest to check that failing to open a file is handled correctly? Or when the disk is full and you try to write to a file?) Furthermore, these exceptional conditions often occur deep inside the call stack, at some low-level utility function that simply does not have the adequate context to know what to do with the error. So the only sane thing to do is to pass the error state back up the stack and let some caller higher up the call stack figure out what to do. Throwing an exception is typically one way of doing this. However, then you run into the problem of how a high-level function knows how to do: because it may be so distant it has no idea that this low-level function was even called, much less what kinds of exceptions it might throw. For example, you could have an I/O error in a buffered write utility function. It throws an exception -- but the caller is an XML generator that's part of some library. It also doesn't know what to do with the exception, other than propagate it, or return some error that XML write failed. So the error is pushed further up -- but the next caller is a document writer module (XML is only a small part of the document), which also doesn't know what to do, because it's called by a function trying to save a backup file. So it has to pass the error along as well. Then it turns out that backup function is called by a script parser that's trying to save a previous state before overwriting it with a new one. And the script parser is called by a macro utility in some spreadsheet application. Now consider the top level function, which is a user-action handler processing a user command to edit a spreadsheet cell. It has no idea that calling updateCell() can eventually call a buffered I/O function that throws an IOError -- so it doesn't even know to catch an IOError. And when the exception occurs, how is it supposed to know what to do? The best scenario at this point is to for it to display some generic error message that editing the cell failed. How is the user supposed to understand why such an apparently simple action as inputting a new value to a cell failed? // The proposed solution in the article is to keep a global error log instead of throwing an exception. So the I/O write function would return a nil object (remember, we're assuming that all the code, including its callers, are written such that the nil object is handled correctly), but in addition, write to a global error log. At some point in the call stack, presumably in the user-action handler, you'd want to know whether the operation succeeded or not. So that's where you'd check whether the error log is empty -- if not, now you have a log of what went wrong (I/O error -> XML write failed -> document save failed -> backup state failed -> script failed -> execute macro failed -> update cell failed), which can help the user understand why the operation failed. Yes, you DO have to eventually check for errors -- but now you can check for it only in a few places: in the actual low-level function that failed and in the top-level function handling user actions, instead of every level down the call stack (which leads to 2^N bifurcating code paths). T
The issue is that our programs cannot always actually be in a valid, correct state (for some definition of correct state) when there is a nil struct or equivalent "None" value. Writing code that assumes None is acceptable from the point it is returned, is formally equivalent to throwing an exception with a catch-all at the start of the program. This first portion of the article feels like the author re-discovered the Maybe monad and is now excited about this "exciting, new way to handle the problem of errors" (the example they gave was SearchTreeForInterestingChain(Node *root)). One way or another programs are going to have paths where there will be errors that must be handled. The quest to minimize these paths is noble and worthwhile, but we all know that it is impossible to "just don't have errors!". The approaches we have in statically typed languages are as follows: 1. Return codes + conditional checks 2. Monadic approaches (sumtypes with then/bind chaining) -- Alternatively implicit monadic bind, as the author did in that function. 3. Exceptions 4. Algebraic effects The approaches we have with dynamically typed languages are the same, except algebraic effects can instead be done far more simply as common lisp style Condition systems (some may know this idea as "resumable exceptions" even though it doesn't actually require an api resembling exceptions) (p.s. every dynamically typed language I touch, the first thing I do is implement a lisp-style condition system for my own use, including in D though that pigeonholes the code to rely on std.variant or Adam's `var` implementation) In my opinion this entire space has already been mapped and solved; it's up to us to be familiar with them and take our pick depending on factors such as performance, and whether library code can handle the problems or if usercode needs to negotiate with library code on correct ways to handle problems. Just don't use return codes.
Jul 15
parent ABrightLight <example example.com> writes:
A quick tldr or rather an attachment to what I wrote:

While the author of that article opts for some sort of error log 
to try to handle the stack issues you described, [it may come as 
no surprise to anyone] the lisp developers have had the right 
solution to that particular problem since approximately 1990.
Jul 15
prev sibling parent reply Robert Collins <robertcollins3737 gmail.com> writes:
On Friday, 3 July 2026 at 23:08:26 UTC, Dennis wrote:
 ...is testing your software. 😉

 There's no shortage of interesting discussions about Error vs. 
 Exception, throwing vs returning, floating point NaN, UTF 
 replacement chars, or the 'correct' error handling mechanism in 
 general.

 The CPU doesn't care about any of this, it just executes 
 instructions to move memory around. The user doesn't care about 
 any of this either, they just want a program that gets the job 
 done. Your task as a programmer is to generate the CPU 
 instructions that do the job the user wants as best as you can. 
 If you find an abstraction that helps handling errors, then use 
 it by all means. But beware that premature abstractions always 
 get things wrong. It's easier to write straightforward code 
 first and see which repetitive patterns naturally emerge from 
 that.

 The other day my X11 desktop application failed to create a 
 graphics context, and as a result it printed a crappy error 
 message and crashed. But wait, I thought I was meticulously 
 checking the return values of al my `glx` calls and returning 
 gracefully? Turns out that before returning the `null` context, 
 X11 calls a default error callback that prints something to the 
 console and aborts. To fix the bad UX, I set `XSetErrorHandler` 
 to my own callback and printed a better error message instead.

 AHA! X11 has a C interface, so this is all the fault of C's 
 lack of good error handling language features. If only they 
 used Exceptions, right? Well, the browser version of my app 
 (running on top of Javascript APIs) also likes to randomly 
 error, for example when the CDN provides and old cached .wasm 
 module instead of the new one. The result: a flooded browser 
 console (which  is hidden by default), while the page stays 
 blank and unresponsive.

 If you catch an Exception and simply show the message in a GUI, 
 the error is usually unhelpful. A recent one I got from Phobos 
 was "Positive Conversion Overflow". Without attaching more 
 information to Exceptions in intermediate catch blocks, the 
 error is devoid of context. (Unless you print the stack trace 
 but it's not like a regular user can make any sense of that)

 But wait, I heard Exceptions are also considered bad these 
 days, the current trendy thing is returning `Result<Value, 
 Error>` types, like Rust does. I don't have that much Rust 
 experience, but I have seen code full of `result.expect("quick 
 message")` (which panics and aborts the program on error) to 
 satisfy the compiler's type checks, with the idea "it's a quick 
 script, I can't be bothered to do proper error handling for 
 this". So you get extra boilerplate in the code, but the UX is 
 no better than a C library with an error callback that prints 
 and aborts, like X11.

 My personal takeaway is that regardless of what language 
 features you use for error handling, you rarely get it right 
 first try. You have to *test your software* by triggering  
 error conditions and observing what the UX of that is. Then it 
 becomes immediately obvious where the error should have been 
 handled and what information should be attached. With concrete 
 feedback, fixing your code becomes so much easier.

 Of course, when you have hundreds of error conditions, this 
 becomes rather tedious. This is where [Walter is completely 
 right](https://forum.dlang.org/post/10utksm$1h2t$1 digitalmars.com) that the
best 'error handling' is *no handling*.

 In [The Easiest Way To Handle Errors Is To Not Have 
 Them](https://www.dgtlgrove.com/p/the-easiest-way-to-handle-errors), Ryan
Fleury gives concrete C examples, but the principles apply to other languages
as well. I haven't read Walter's book recommendation "A Philosophy of Software
Design" yet, and if you haven't either, maybe that post is more accessible.
I agree with the idea that error handling should be evaluated from the user's perspective rather than focusing only on the programming pattern. Whether you're using exceptions, result types, or error codes, none of them matter if the application leaves users confused when something goes wrong. We ran into this while improving one of our own web applications. Instead of only catching exceptions, we started testing common failure scenarios such as invalid input, missing resources, API timeouts, and permission issues. We also separated user-friendly messages from detailed logs, so users received clear guidance while developers still had enough information to troubleshoot the root cause. One thing that made a noticeable difference was treating error handling as part of QA rather than something to add at the end of development. Running through failure scenarios before every release exposed several issues that normal testing never caught. We documented the approach while working on a completely different project, and some of the lessons also applied to [water damage knoxville].
Jul 09
next sibling parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 09/07/2026 7:32 PM, Robert Collins wrote:
 I agree with the idea that error handling should be evaluated from the 
 user's perspective rather than focusing only on the programming pattern. 
 Whether you're using exceptions, result types, or error codes, none of 
 them matter if the application leaves users confused when something goes 
 wrong.
 
 We ran into this while improving one of our own web applications. 
 Instead of only catching exceptions, we started testing common failure 
 scenarios such as invalid input, missing resources, API timeouts, and 
 permission issues. We also separated user-friendly messages from 
 detailed logs, so users received clear guidance while developers still 
 had enough information to troubleshoot the root cause.
 
 One thing that made a noticeable difference was treating error handling 
 as part of QA rather than something to add at the end of development. 
 Running through failure scenarios before every release exposed several 
 issues that normal testing never caught.
 
 We documented the approach while working on a completely different 
 project, and some of the lessons also applied to [water damage knoxville].
This shouldn't be a surprise to anyone. Testers aren't some rando you got off the street. They have actual qualifications that are internationally recognized for their roles. https://istqb.org/ Microsoft also had one, but that got retired (*sigh*). These qualifications were very well established in industry by the time 2013 came around when I encountered it during my degree. The expertise to test the human side of programs is extremely well understood in the literature, but it does get missed as its 'soft' skills. There is also requirements and design testing in the form of Human Computer Interaction scientists. Sadly that doesn't have qualifications that are internationally recognized, but many universities do have them. I.e. https://online.stanford.edu/programs/human-computer-interaction-graduate-certificate
Jul 09
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 7/9/2026 12:32 AM, Robert Collins wrote:
 Instead of 
 only catching exceptions, we started testing common failure scenarios such as 
 invalid input, missing resources, API timeouts, and permission issues. We also 
 separated user-friendly messages from detailed logs, so users received clear 
 guidance while developers still had enough information to troubleshoot the
root 
 cause.
One testing technique that works well is to have input objects, processing code, and output objects. One creates "mock" input objects, which can be used to generate specific bad input. Then write "mock" output objects that, instead of reporting the error, compare the error against what the expected error would be. You can see this in action in the lexer unittests in the dmd compiler.
Jul 09
parent "H. S. Teoh" <hsteoh qfbox.info> writes:
On Thu, Jul 09, 2026 at 04:26:03PM -0700, Walter Bright via Digitalmars-d wrote:
 On 7/9/2026 12:32 AM, Robert Collins wrote:
 Instead of only catching exceptions, we started testing common
 failure scenarios such as invalid input, missing resources, API
 timeouts, and permission issues. We also separated user-friendly
 messages from detailed logs, so users received clear guidance while
 developers still had enough information to troubleshoot the root
 cause.
One testing technique that works well is to have input objects, processing code, and output objects. One creates "mock" input objects, which can be used to generate specific bad input. Then write "mock" output objects that, instead of reporting the error, compare the error against what the expected error would be. You can see this in action in the lexer unittests in the dmd compiler.
Mock objects are awesome for unittests. I've taken advantage of D templates to generate instantiations of functions that use mock objects instead of real OS syscalls, in order to ensure the logic is sound. For example: auto myFunc(File = std.stdio.File)(File input, File output) { ... } unittest { struct MockFile { // insert stuff to inject errors & various // conditions } MockFile mockInput = ...; MockFile mockOutput = ...; auto ret = myFunc(mockInput, mockOutput); assert(ret == expectedReturn); ... // check of MockFile state is as expected } When compiling with -unittest, the mock version of myFunc is instantiated and gets called by the unittest. When not compiling with -unittest, only the non-mock version of myFunc is generated, so there's no template bloat in the release build. This technique can be used not just for files; with suitable template parameters I've managed to write unittests for functions that manipulate the filesystem -- by creating a mock filesystem (with minimal functionality, just enough to run the desired tests). T -- May you live all the days of your life. -- Jonathan Swift
Jul 09