digitalmars.D - The solution to "Error handling"...
- Dennis (56/56) Jul 03 ...is testing your software. 😉
- H. S. Teoh (29/35) Jul 03 That post is insightful indeed.
- Guillaume Piolat (3/6) Jul 04 Are we going to read this suggestion over and over for the next
- Richard (Rikki) Andrew Cattermole (7/14) Jul 04 Actually it does!
- Walter Bright (8/9) Jul 04 If you want to initialize to 0:
- Richard (Rikki) Andrew Cattermole (4/16) Jul 04 This is not about being able to do it, nor is it an argument for the
- Adam D. Ruppe (7/11) Jul 04 Yup, this is what tipped opend over to change all built in types
- monkyyy (24/31) Jul 04 ```d
- Walter Bright (8/11) Jul 03 I thought I would never see this in the forum! I assumed that you were w...
- Jonathan M Davis (9/12) Jul 04 I'm not sure that I'd say that it sounds all wrong so much as it's often
- Walter Bright (3/3) Jul 05 I agree it does not seem to be straightforward, as evidenced by the vari...
- Richard (Rikki) Andrew Cattermole (10/15) Jul 05 It already has as much as it will.
- monkyyy (9/13) Jul 05 Uncle bob is functionally a con artist.
- =?UTF-8?Q?Ali_=C3=87ehreli?= (21/31) Jul 03 I used a set of macros to print a stack of error messages.
- Jonathan M Davis (20/22) Jul 04 Well, types in general don't have zero initialization. It's just the int...
- Walter Bright (5/8) Jul 04 Here's a nice explanation of why NaNs are needed:
- Jonathan M Davis (30/38) Jul 04 I'm aware, but IMHO, the result is a disgusting mess. But honestly, I ha...
- Walter Bright (35/35) Jul 04 Of course. The issue is really that floating point is the best we can do...
- H. S. Teoh (50/55) Jul 04 Mostly, but not 100% true. Turns out, it *is* possible to represent
- Walter Bright (17/43) Jul 05 It still applies. People will still write:
- Alexandru Ermicioi (3/7) Jul 05 Maybe D should drop default initialization completely?
- Richard (Rikki) Andrew Cattermole (10/18) Jul 05 Absolutely not.
- Zz (4/10) Jul 05 Have you looked at this.
- Kapendev (14/26) Jul 05 It's a good first option when making a new type.
...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
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
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
On 04/07/2026 9:51 PM, Guillaume Piolat wrote:On Friday, 3 July 2026 at 23:57:05 UTC, H. S. Teoh wrote: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.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
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
On 05/07/2026 6:56 AM, Walter Bright wrote:On 7/4/2026 3:02 AM, Richard (Rikki) Andrew Cattermole wrote: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.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
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
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
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
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
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
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
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
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>` typesI don't know what Error is for so I returned e.g. ReturnValue<int>.like Rust does.No, I did it my way. :DI 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
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
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
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: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 DavisAnd 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
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
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
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:I didn't know that! Nice read. Thank you for posting it.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. [...]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.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.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
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
On 05/07/2026 8:26 PM, Alexandru Ermicioi wrote:On Sunday, 5 July 2026 at 08:11:31 UTC, Walter Bright wrote: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.ddIt still applies. People will still write: ```d float f; ```Maybe D should drop default initialization completely? Then this issue will disappear.
Jul 05
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 DavisHave you looked at this. https://zylinski.se/posts/a-programming-language-for-me/#zero-is-initialized-zii Zz
Jul 05
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: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; } ```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 DavisHave you looked at this. https://zylinski.se/posts/a-programming-language-for-me/#zero-is-initialized-zii Zz
Jul 05









"Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> 