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
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 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
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 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
parent "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
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