digitalmars.D - Why static analysis is the way to go
- H. S. Teoh (21/21) May 28 Preaching to the choir here, but this again shows why static analysis
- Richard (Rikki) Andrew Cattermole (11/35) May 28 Aw, no wish list?
- H. S. Teoh (26/48) May 28 [...]
- Richard (Rikki) Andrew Cattermole (2/8) May 28 https://github.com/dlang/dmd/issues/17906
- H. S. Teoh (19/29) May 28 But that bug has like 3-4 PRs referencing it. Surely we're making
- Richard (Rikki) Andrew Cattermole (7/40) May 28 nothrow @nogc pure scope return all suffer from this.
- Walter Bright (4/5) May 29 I recall an old Dr Pepper commercial where the line was "Be a Pepper, dr...
- Walter Bright (2/4) May 29 No fair posting things like this without a linky!
- Walter Bright (9/14) May 29 Doing inference 100% requires dealing with recursive data flow. The fron...
- Richard (Rikki) Andrew Cattermole (16/31) May 29 Except that isn't the full story.
- monkyyy (3/5) May 29 Got an example of a useful thing?
- Richard (Rikki) Andrew Cattermole (3/9) May 29 Anything effect based.
- monkyyy (102/111) May 29 a) exceptions airnt useful
- Meta (5/19) May 29 I dunno why everyone hates exceptions. They're incredibly useful,
- Nick Treleaven (4/10) May 30 Far too easy to forget that calling a function may throw an
- H. S. Teoh (10/21) May 30 Auto-decoding is widely regarded as a mistake, as is throwing an
- monkyyy (2/22) May 30 its a bad goto, requiring cleanup and a "comefrom" statement
- Walter Bright (4/7) May 30 "A Philosophy of Software Design" by Ousterhout has a chapter on it: "Wh...
- Walter Bright (21/27) May 30 Of course. For recursive data structures, the path you take determines t...
- Richard (Rikki) Andrew Cattermole (14/19) May 29 And yet:
- monkyyy (10/33) May 28 99.99% of the effect is api and type theory: slices being built
- H. S. Teoh (91/110) May 28 [...]
- Dennis (6/9) May 28 No
- monkyyy (30/41) May 28 I cast that away anytime it gets in my way, the biggest thing is
- H. S. Teoh (103/135) May 28 You're welcome to redefine your own string type as char[] and see how
- monkyyy (24/46) May 28 Im asserting I write smaller programs then most, without @safe,
- H. S. Teoh (48/77) May 28 Well, that explains a lot. :-D If you mostly write one-man-show small
- monkyyy (12/50) May 28 https://monkyyy.itch.io/frut-gam3
- Kapendev (2/15) May 28
- H. S. Teoh (21/23) May 29 Big projects are not my choice. My boss assigned me to work on a 2M LOC
- Walter Bright (11/13) May 29 The fatal flaw with C++ iterators is you always need two pieces of data,...
- monkyyy (4/7) May 29 if that was the goal, the lack of indexing is damning
- Walter Bright (17/21) May 29 Reminds me of this C loop:
Preaching to the choir here, but this again shows why static analysis and compiler-enforced checks at compile-time is the way to go: https://www.zdnet.com/article/rust-will-save-linux-from-ai-says-greg-kroah-hartman/ The Linux kernel is a pretty large C codebase, and I also work with a pretty large C codebase in my day job. At that scale, the weaknesses of C persistently come up: forgetting to free a pointer after use, forgetting to release a resource (often in a rare path not often or never tested), buffer overruns, dangling pointers, etc.. After decades of working on a large complex C codebase, I've grown weary of debugging these same old problems over, and over, and over again. D made a lot of right choices in this area: statically-verifiable const, compiler-enforced nothrow, pure, etc., arrays that always carry length and out-of-bounds deference causing a runtime exception instead of overwriting arbitrary memory, GC eliminating an entire class of pointer bugs, etc.. These make D a huge pleasure to work with, as opposed to the constant stream of pointer bugs, memory leaks, and programming-by-convention that has been proven to be ineffective decades ago, that you have to put up with when working in C. T -- What starts with O and ends with NIONS and sometimes makes you cry? Opinions.
May 28
On 29/05/2026 7:41 AM, H. S. Teoh wrote:Preaching to the choir here, but this again shows why static analysis and compiler-enforced checks at compile-time is the way to go: https://www.zdnet.com/article/rust-will-save-linux-from-ai-says-greg-kroah-hartman/ The Linux kernel is a pretty large C codebase, and I also work with a pretty large C codebase in my day job. At that scale, the weaknesses of C persistently come up: forgetting to free a pointer after use, forgetting to release a resource (often in a rare path not often or never tested), buffer overruns, dangling pointers, etc.. After decades of working on a large complex C codebase, I've grown weary of debugging these same old problems over, and over, and over again. D made a lot of right choices in this area: statically-verifiable const, compiler-enforced nothrow, pure, etc., arrays that always carry length and out-of-bounds deference causing a runtime exception instead of overwriting arbitrary memory, GC eliminating an entire class of pointer bugs, etc.. These make D a huge pleasure to work with, as opposed to the constant stream of pointer bugs, memory leaks, and programming-by-convention that has been proven to be ineffective decades ago, that you have to put up with when working in C. TAw, no wish list? Also please note that nothrow nogc and pure all rely on inference to work correctly, and dmd is unable to do this. As a result I would suggest to think of them as being on the chopping block at a future point in time. We keep finding problems with them, nothrow in particular had a bug report within the last couple of months that we have yet to find a solution for. I will be wanting a replacement however, so no need to worry about any removal, just the solution will have to be different ;)
May 28
On Fri, May 29, 2026 at 08:36:54AM +1200, Richard (Rikki) Andrew Cattermole via Digitalmars-d wrote:On 29/05/2026 7:41 AM, H. S. Teoh wrote:[..][...]https://www.zdnet.com/article/rust-will-save-linux-from-ai-says-greg-kroah-hartman/[...]D made a lot of right choices in this area: statically-verifiable const, compiler-enforced nothrow, pure, etc., arrays that always carry length and out-of-bounds deference causing a runtime exception instead of overwriting arbitrary memory, GC eliminating an entire class of pointer bugs, etc.. These make D a huge pleasure to work with, as opposed to the constant stream of pointer bugs, memory leaks, and programming-by-convention that has been proven to be ineffective decades ago, that you have to put up with when working in C.Aw, no wish list? Also please note that nothrow nogc and pure all rely on inference to work correctly, and dmd is unable to do this. As a result I would suggest to think of them as being on the chopping block at a future point in time.I wasn't thinking of these attributes in particular, but the general idea of compiler-enforced static verification. I think the last 50 years have taught us beyond all doubt that humans are fallible, and that catching problems at compile-time is far better than discovering them at runtime. Compile-time guarantees are a big advantage, whatever form they may take. Not only do they help catch stupid mistakes early, they also make it easier to reason about code, which is a boon for maintainability. Often, knowing what the code *cannot* do is equally as important as knowing what it can do.We keep finding problems with them, nothrow in particular had a bug report within the last couple of months that we have yet to find a solution for.Just curious, which bug is that?I will be wanting a replacement however, so no need to worry about any removal, just the solution will have to be different ;)I'm not particularly attached to any of them specifically, but I would warmly welcome more compiler inference and compile-time enforcement. External static analyzers are all good and everything, but if something is not enforced, it might as well not exist, because human laziness will trump it and *somebody* *somewhere* will have the smart idea of bypassing it (because an irate customer wants the fix by yesterday, and fixing it the right way takes too much time), with the usual disastrous consequences. T -- Why can't you just be a nonconformist like everyone else? -- YHL
May 28
On 29/05/2026 10:17 AM, H. S. Teoh wrote:
We keep finding problems with them, nothrow in particular had a bug
report within the last couple of months that we have yet to find a
solution for.
Just curious, which bug is that?
https://github.com/dlang/dmd/issues/17906
May 28
On Fri, May 29, 2026 at 10:27:48AM +1200, Richard (Rikki) Andrew Cattermole via Digitalmars-d wrote:On 29/05/2026 10:17 AM, H. S. Teoh wrote:But that bug has like 3-4 PRs referencing it. Surely we're making progress? OTOH, inferrence has never been dmd's strong suit. Throw(ha!) in recursion, and I'm not surprised it got all fouled up. This is somewhat, albeit remotely, related to the whole inheritable attributes thing. Ideally, you want a wildcard value representing "the nothrow-ness of this function" so that you can reason about recursive calls in a sane way afterwards. But there's no such thing in dmd, so you have just a bunch of hacks to try to skirt around the issue somehow. I wouldn't be surprised if similar issues exist with the other function attributes when the function is recursive. If .fun calls itself, is .fun pure? Well that depends on whether .fun pure to begin with. Has anyone tried recursion that isn't immediately obvious, like .fun -> .gun -> .fun, each level of which has nothrow-ness that depends on the callee? Betcha that doesn't work either. T -- There are two ways to write error-free programs; only the third one works.We keep finding problems with them, nothrow in particular had a bug report within the last couple of months that we have yet to find a solution for. Just curious, which bug is that?https://github.com/dlang/dmd/issues/17906
May 28
On 29/05/2026 11:09 AM, H. S. Teoh wrote:On Fri, May 29, 2026 at 10:27:48AM +1200, Richard (Rikki) Andrew Cattermole via Digitalmars-d wrote:nothrow nogc pure scope return all suffer from this. dmd's architecture can't do inference as part of the type system. But it allows it to be fast. This was the final nail that decided that DIP1000 wasn't going to be turned on. Ideally I'd do a dconf talk about this, but alas, I did not win Lotto.On 29/05/2026 10:17 AM, H. S. Teoh wrote:But that bug has like 3-4 PRs referencing it. Surely we're making progress? OTOH, inferrence has never been dmd's strong suit. Throw(ha!) in recursion, and I'm not surprised it got all fouled up. This is somewhat, albeit remotely, related to the whole inheritable attributes thing. Ideally, you want a wildcard value representing "the nothrow-ness of this function" so that you can reason about recursive calls in a sane way afterwards. But there's no such thing in dmd, so you have just a bunch of hacks to try to skirt around the issue somehow. I wouldn't be surprised if similar issues exist with the other function attributes when the function is recursive. If .fun calls itself, is .fun pure? Well that depends on whether .fun pure to begin with. Has anyone tried recursion that isn't immediately obvious, like .fun -> .gun -> .fun, each level of which has nothrow-ness that depends on the callee? Betcha that doesn't work either. TWe keep finding problems with them, nothrow in particular had a bug report within the last couple of months that we have yet to find a solution for. Just curious, which bug is that?https://github.com/dlang/dmd/issues/17906
May 28
On 5/28/2026 3:17 PM, H. S. Teoh wrote:Why can't you just be a nonconformist like everyone else? -- YHLI recall an old Dr Pepper commercial where the line was "Be a Pepper, drink Dr Pepper, join us, the non-conformists!" I enjoyed the sly humor of it.
May 29
On 5/28/2026 1:36 PM, Richard (Rikki) Andrew Cattermole wrote:We keep finding problems with them, nothrow in particular had a bug report within the last couple of months that we have yet to find a solution for.No fair posting things like this without a linky!
May 29
On 5/28/2026 1:36 PM, Richard (Rikki) Andrew Cattermole wrote:Also please note that nothrow nogc and pure all rely on inference to work correctly, and dmd is unable to do this. As a result I would suggest to think of them as being on the chopping block at a future point in time.Doing inference 100% requires dealing with recursive data flow. The front end doesn't do that. However, in cases where recursion happens, the compiler takes a conservative approach and assumes that the attribute doesn't apply. This ensures the code is not broken. The easy workaround is to simply explicitly put the attribute on the recursive function. This is not a disastrous defect.We keep finding problems with them, nothrow in particular had a bug report within the last couple of months that we have yet to find a solution for.That bug report is not about nothrow.
May 29
On 30/05/2026 6:05 AM, Walter Bright wrote:On 5/28/2026 1:36 PM, Richard (Rikki) Andrew Cattermole wrote:Except that isn't the full story. Dmd is inherently first come first served. Ordering matters. Swap the order of two declarations and errors can appear and disappear as information is acquired. We've mostly tuned the language to not exhibit these problems by simply being eager, and then you get cyclic and other awful errors. There are somethings that we can't do in our type system, anything that requires inference for say effects is the big ticket item that is out. It may be unfortunate, but it does allow us to be fast at least, so not all bad. Realistically the best thing we can do is say if you want effects modelling, its gonna cost and be opt-in. Those who don't care, will get the speed, and those that do care will happily pay. A much better approach than making people suffer from attribute soup and PhobosV3 going: BAN ALL ATTRIBUTES! lolAlso please note that nothrow nogc and pure all rely on inference to work correctly, and dmd is unable to do this. As a result I would suggest to think of them as being on the chopping block at a future point in time.Doing inference 100% requires dealing with recursive data flow. The front end doesn't do that. However, in cases where recursion happens, the compiler takes a conservative approach and assumes that the attribute doesn't apply. This ensures the code is not broken. The easy workaround is to simply explicitly put the attribute on the recursive function. This is not a disastrous defect.
May 29
On Saturday, 30 May 2026 at 03:02:37 UTC, Richard (Rikki) Andrew Cattermole wrote:There are somethings that we can't do in our type system,Got an example of a useful thing?
May 29
On 30/05/2026 3:18 PM, monkyyy wrote:On Saturday, 30 May 2026 at 03:02:37 UTC, Richard (Rikki) Andrew Cattermole wrote:Anything effect based. I.e. value type exceptions.There are somethings that we can't do in our type system,Got an example of a useful thing?
May 29
On Saturday, 30 May 2026 at 03:45:46 UTC, Richard (Rikki) Andrew Cattermole wrote:On 30/05/2026 3:18 PM, monkyyy wrote:a) exceptions airnt useful b) function coloring is bad c) with a ct counter, which I have 2 methods of making, you can make a nice open-sumtype, throw an opensumtype into an exception and you can grab that data in the catch block; you can just do this: ```d import std; import core.stdc.stdlib; struct tuple(T...){ T me; alias me this; } struct opensumtype{ enum counter=cast(immutable(void)*)[0].ptr; static auto getcount()=>(*(cast(int*)counter)); static auto count()=>(*(cast(int*)counter))++; enum mytypeid(T...)=count(); void* where; int what=mytypeid!null; this(T...)(T args){ where=malloc(tuple!T.sizeof); alias S=tuple!T; *cast(S*)where=args; what=mytypeid!T; } ref tuple!T get(T...)(){ assert(has!T); return *cast(typeof(return)*)where; } bool has(T...)()=>what==mytypeid!T; } unittest{ opensumtype foo; foo=opensumtype(3); assert(foo.has!int); assert(foo.get!int[0]==3); foo=opensumtype(3.14,"hello",1000); assert(foo.get!(double,string,int)==tuple!(double,string,int)(3.14,"hello",1000)); } class valueexception : Exception{ enum counter=cast(immutable(void)*)[0].ptr; static auto getcount()=>(*(cast(int*)counter)); static auto countup()=>(*(cast(int*)counter))++; static auto countdown()=>(*(cast(int*)counter))--; opensumtype values; static void areexceptionshandled(string file = __FILE__, size_t line = __LINE__){ assert(getcount()==0,"some exceptions are not handled"); } void handled(string file = __FILE__, size_t line = __LINE__)(){ enum __=countdown(); } this(string file = __FILE__, size_t line = __LINE__,T...)(string msg,T args){ enum __=countup(); values=opensumtype(args); super(msg,file,line,null); } } void fileio1(string s){ if(s=="rm -rf *"){ throw new valueexception("attempted deleted harddrive",">:("); }} unittest{ try{ fileio1("rm -rf *"); } catch (valueexception e){ e.values.get!string[0].writeln; e.handled(); } } struct importantcustomeerdata{ string name="john"; int balence=500; bool onfbiwatchlist=true; } enum transactionstatus{pending,done} void fileio2(string s,importantcustomeerdata data){ if(s=="rm -rf *"){ throw new valueexception("attempted deleted harddrive",data,transactionstatus.pending); }} unittest{ try{ fileio2("rm -rf *",importantcustomeerdata("mary")); } catch (valueexception e){ assert(e.values.has!(importantcustomeerdata,transactionstatus)); e.values.get!(importantcustomeerdata,transactionstatus).writeln; //e.handled(); }} unittest{ valueexception.areexceptionshandled(); } ``` d) (it just be better to throw any "important data" into a file tho; now you have two problems)On Saturday, 30 May 2026 at 03:02:37 UTC, Richard (Rikki) Andrew Cattermole wrote:Anything effect based. I.e. value type exceptions.There are somethings that we can't do in our type system,Got an example of a useful thing?
May 29
On Saturday, 30 May 2026 at 06:14:25 UTC, monkyyy wrote:On Saturday, 30 May 2026 at 03:45:46 UTC, Richard (Rikki) Andrew Cattermole wrote:I dunno why everyone hates exceptions. They're incredibly useful, and generally significantly reduce the complexity of your code. Like pretty much everything in programming, misusing them will of course cause major issues.On 30/05/2026 3:18 PM, monkyyy wrote:a) exceptions airnt usefulOn Saturday, 30 May 2026 at 03:02:37 UTC, Richard (Rikki) Andrew Cattermole wrote:Anything effect based. I.e. value type exceptions.There are somethings that we can't do in our type system,Got an example of a useful thing?
May 29
On Saturday, 30 May 2026 at 06:43:13 UTC, Meta wrote:On Saturday, 30 May 2026 at 06:14:25 UTC, monkyyy wrote:Far too easy to forget that calling a function may throw an exception. And 'misuse' is so widespread, it's even in Phobos with auto-decoding.a) exceptions airnt usefulI dunno why everyone hates exceptions. They're incredibly useful, and generally significantly reduce the complexity of your code. Like pretty much everything in programming, misusing them will of course cause major issues.
May 30
On Sat, May 30, 2026 at 10:55:41AM +0000, Nick Treleaven via Digitalmars-d wrote:On Saturday, 30 May 2026 at 06:43:13 UTC, Meta wrote:Auto-decoding is widely regarded as a mistake, as is throwing an exception during Unicode decoding. But I also find exceptions useful -- when used properly, for actual exceptional situations, not a lazy way of returning a failure value. The current *implementation* of exceptions via libunwind may not be ideal, but the concept IMO is sound. T -- What starts with O and ends with NIONS and sometimes makes you cry? Opinions.On Saturday, 30 May 2026 at 06:14:25 UTC, monkyyy wrote:Far too easy to forget that calling a function may throw an exception. And 'misuse' is so widespread, it's even in Phobos with auto-decoding.a) exceptions airnt usefulI dunno why everyone hates exceptions. They're incredibly useful, and generally significantly reduce the complexity of your code. Like pretty much everything in programming, misusing them will of course cause major issues.
May 30
On Saturday, 30 May 2026 at 06:43:13 UTC, Meta wrote:On Saturday, 30 May 2026 at 06:14:25 UTC, monkyyy wrote:its a bad goto, requiring cleanup and a "comefrom" statementOn Saturday, 30 May 2026 at 03:45:46 UTC, Richard (Rikki) Andrew Cattermole wrote:I dunno why everyone hates exceptions. They're incredibly useful, and generally significantly reduce the complexity of your code. Like pretty much everything in programming, misusing them will of course cause major issues.On 30/05/2026 3:18 PM, monkyyy wrote:a) exceptions airnt usefulOn Saturday, 30 May 2026 at 03:02:37 UTC, Richard (Rikki) Andrew Cattermole wrote:Anything effect based. I.e. value type exceptions.There are somethings that we can't do in our type system,Got an example of a useful thing?
May 30
On 5/29/2026 11:43 PM, Meta wrote:I dunno why everyone hates exceptions. They're incredibly useful, and generally significantly reduce the complexity of your code. Like pretty much everything in programming, misusing them will of course cause major issues."A Philosophy of Software Design" by Ousterhout has a chapter on it: "Why exceptions add complexity". It's an interesting read, even if you don't agree with it.
May 30
On 5/29/2026 8:02 PM, Richard (Rikki) Andrew Cattermole wrote:Dmd is inherently first come first served. Ordering matters. Swap the order of two declarations and errors can appear and disappear as information is acquired.Of course. For recursive data structures, the path you take determines the result. That's why the DMD optimizer uses DFA to solve the data flow equations iteratively. Most of the time, it converges on the solution in only one pass through the equations. The same is true for the front end, which does only one pass. Most of the time, it gets it right. When it doesn't get it right, it takes the conservative path which does not violate the typing system constraints.We've mostly tuned the language to not exhibit these problems by simply being eager, and then you get cyclic and other awful errors.Yes, you can get cyclic errors like: ```c struct S { int x = X; static if (X) enum X = 0; else enum X = 1; } ``` Some of these the compiler can diagnose. But in general, coding such things is a bad idea.
May 30
On 30/05/2026 6:05 AM, Walter Bright wrote:
We keep finding problems with them, nothrow in particular had a bug
report within the last couple of months that we have yet to find a
solution for.
That bug report is not about nothrow.
And yet:
add more flags to FuncDeclaration. adamdruppe
nothrow is special because it is computed at the end of a function with
funcdecl.fbody.blockExit(funcdecl, ...). But before that, the catch
block deletion is performed which also uses blockExit(func). The func
parameter is used to infer/check nothrow for func, which allows
recursive calls, which causes the bug: Allowing recursive calls is only
sound when checking the entire body, not a subset of it. In the bug
report, the throw is indeed outside the scope of the try-catch
statement. This PR's solution is to not pass func to blockExit when used
on only the try statement." - Dennis
Merged fix: https://github.com/dlang/dmd/pull/23194
May 29
On Thursday, 28 May 2026 at 19:41:27 UTC, H. S. Teoh wrote:Preaching to the choir here, but this again shows why static analysis and compiler-enforced checks at compile-time is the way to go: https://www.zdnet.com/article/rust-will-save-linux-from-ai-says-greg-kroah-hartman/ The Linux kernel is a pretty large C codebase, and I also work with a pretty large C codebase in my day job. At that scale, the weaknesses of C persistently come up: forgetting to free a pointer after use, forgetting to release a resource (often in a rare path not often or never tested), buffer overruns, dangling pointers, etc.. After decades of working on a large complex C codebase, I've grown weary of debugging these same old problems over, and over, and over again. D made a lot of right choices in this area: statically-verifiable const, compiler-enforced nothrow, pure, etc., arrays that always carry length and out-of-bounds deference causing a runtime exception instead of overwriting arbitrary memory, GC eliminating an entire class of pointer bugs, etc.. These make D a huge pleasure to work with, as opposed to the constant stream of pointer bugs, memory leaks, and programming-by-convention that has been proven to be ineffective decades ago, that you have to put up with when working in C. T99.99% of the effect is api and type theory: slices being built and foreach being overloadable with ranges, ranges being there This is not static analysis, I dont use any static analysis keywords that would cause any of it to be inside my code, yet Im not running into c like segfaults every time I write string code Hot take, 70% of it of my avoidance of segfault comes from foreach being able to use range alone; if Phobos wasn't there but the way I made datastructures was front,pop, empty; I would not make segfaults.
May 28
On Thu, May 28, 2026 at 09:30:32PM +0000, monkyyy via Digitalmars-d wrote:On Thursday, 28 May 2026 at 19:41:27 UTC, H. S. Teoh wrote:[...][...]https://www.zdnet.com/article/rust-will-save-linux-from-ai-says-greg-kroah-hartman/[...]D made a lot of right choices in this area: statically-verifiable const, compiler-enforced nothrow, pure, etc., arrays that always carry length and out-of-bounds deference causing a runtime exception instead of overwriting arbitrary memory, GC eliminating an entire class of pointer bugs, etc.. These make D a huge pleasure to work with, as opposed to the constant stream of pointer bugs, memory leaks, and programming-by-convention that has been proven to be ineffective decades ago, that you have to put up with when working in C.99.99% of the effect is api and type theory: slices being built and foreach being overloadable with ranges, ranges being thereSlices/arrays carrying length wherever they go is a HUGE benefit in retrospect. I remember the early days of arguments about "wasting" precious bytes by passing array lengths around all the time where "a simple pointer would do the job". And also arguments about "wasting" precious CPU cycles performing bounds checks. That was so shortsighted. :-D Moore's Law ensured such arguments would become irrelevant. The whole concept behind ranges, while seemingly simple in retrospect, neatly abstract the idea of iteration in a far-reaching scope that eliminates 90% of the for-loops I write. Loop conditions are notoriously hard to get right, and humans are bad at repetitive tasks like writing iteration from 0 to N. Or was it 1 to N-1? Or maybe 1 to N? Or 0 to N-1? Abstracting this away eliminates an entire subclass of off-by-1 errors, in addition to opening up new avenues of expressivity. Every time I write C I miss being able to write a UFCS chain to massage my data into a printable form -- in C it's a painful diversion from what you really want to be focusing on -- finding the bug -- and writing a separate function just to dump data in a human-readable way. In D, it's 2 seconds writing a UFCS chain and you go back to debugging immediately. Huge difference. And then there's the decision to use GC: the haters will hate, but having a GC at my disposal simplified like 99% of my APIs. Clean APIs lead to less bugs, more composibility, and more reusability. Recently I had to implement a new API in C, and it was painful. At every turn you have to worry about whether/how buffers would be passed, who would be responsible for allocation, who for deallocation, what to do in case of errors, ad nauseum. Should I return a pointer to a static string buffer? Should I allocate and hope my caller doesn't forget to free? Should I ask instead for a buffer from the caller to fill? So much mental effort is expended on such peripheral yet important questions. And then afterwards you discover to your chagrin that you've actually managed to FORGET about one important detail, and your program just crashed because of a dangling pointer. Again. In D, I can just allocate and return, or return a pointer to a static buffer, or whatever -- IT DOESN'T CHANGE THE API. That's huge. I can totally retrofit my function with a new allocation mechanism and the callers don't even have to know -- it will JUST WORK. The haters can hate, but I still love my GC.This is not static analysis, I dont use any static analysis keywords that would cause any of it to be inside my code, yet Im not running into c like segfaults every time I write string codeYou *do* realize that every time you write string code in D, you're using static analysis, right? ;-) Remember, string = immutable(char)[]. That "tiny" decision to stick "immutable" on it is what makes D strings so straightforward to use. The fact that immutable is compile-time enforced means nobody can sneakily mutate stuff behind your back and ruin your algorithms; your code can count on the string staying the way it is and not suddenly mutating into something else, breaking your assumptions, and causing your code to crash. Without that, your string APIs would be full of problems with aliasing and functions trampling over each other's strings, leading to a massive mess like in C, where, when reading string manipulation code, the back of your mind is constantly wondering, is this going to overrun the buffer? Is the buffer being aliased? Why is this code modifying what's supposed to be an immutable identifier? Will this affect some code out there that assumes strings don't change? Why does this function take non-const? Now I have to write yet another cast. Cross your fingers that some idiot in the future (namely, yours truly 2 months later) doesn't start mutating it and causing a crash in an unrelated module. And then out of paranoia you start strcpy'g your strings everywhere, and Schlemiel starts showing up in your performance benchmarks. Remember back in the day when somebody had the bright idea to make AA's more "user-friendly" by allowing const or even mutable arrays as keys? Yeah, the aftermath was NOT pretty. Took us at least a couple of years to clean up the mess amid user complaints like "why did my key suddenly disappear from my AA?" and "this key is obviously in the AA why isn't it finding it?!".Hot take, 70% of it of my avoidance of segfault comes from foreach being able to use range alone; if Phobos wasn't there but the way I made datastructures was front,pop, empty; I would not make segfaults.Retrospect is always 20/20 as they say. Before Columbus made the egg stand upright nobody thought it was possible; afterwards they dismissed it with "well of course that's obviously how you do it!". Now that we're used to range-based UFCS chains, it seems to be the most obvious way of thinking about your algorithms. Whereas before, the big picture was drowned out by the nitty-gritty of initializing and bumping your loop variable and writing the right loop conditions so that you exit at the right time without off-by-1 errors. The *idea* of ranges elevated our thinking to a whole 'nother level, that it doesn't even make any sense to go back to thinking about loop counter bumping anymore. Besides, segfaults are only a tiny part of what D eliminates by design. D's GC, complain about it as anyone might, has nevertheless saved me hours, no, days and weeks, of chasing down pointer bugs and use-after-free errors. It eliminated an entire class of bugs from my code, not to mention make my APIs cleaner so callers are less likely to use it wrongly. And it removed the huge mental load of having to worry about memory management issues all the danged time, which you have to do every time you write non-trivial C code, and freed up mental resources to actually, y'know, focus on the problem you're supposed to be solving with your code instead of micromanaging memory management nitty-gritty. T -- Latin's a dead language, as dead as can be; it killed off all the Romans, and now it's killing me! -- Schoolboy
May 28
On Thursday, 28 May 2026 at 22:58:33 UTC, H. S. Teoh wrote:Should I return a pointer to a static string buffer?NoShould I allocate and hope my caller doesn't forget to free?NoShould I ask instead for a buffer from the caller to fill?Maybe, but an Arena* parameter is better https://www.dgtlgrove.com/p/untangling-lifetimes-the-arena-allocator But yeah GC is the easiest API to use.
May 28
On Thursday, 28 May 2026 at 22:58:33 UTC, H. S. Teoh wrote:Remember, string = immutable(char)[]. That "tiny" decision to stick "immutable" on it is what makes D strings so straightforward to use.I cast that away anytime it gets in my way, the biggest thing is that the os is picky about me modifying strings from the executable, the run time couldve just handled that case and char[] wouldve been better for meRemember back in the day when somebody had the bright idea to make AA's more "user-friendly" by allowing const or even mutable arrays as keys?no I dont, lets allow mutable keys :)The *idea* of ranges elevated our thinking to a whole 'nother level,the *type theory* of iterators; steponov was probaly being too mathy when he defined it and aa copied the hierarchy from stl. But it was a bunch of uml boxes and thinking about some variants of linked lists and he was trying to imagine what could be made to work with c++ operator overloading. Its definitions from operations, quite explicitly, and steponov fairly clear about how he was thinking about it in his rants. And yet it still has flaws as Ive tried to explain, there is the footgun around indexing. Which is still a type theory error.Besides, segfaults are only a tiny part of what D eliminates by design.**But you linked a pro-rust article and called for static analyis**, and rusts solution to memory management is to make a proof engine. And like no, no, just no. Poor little rikki here thinks d needs some math prover thing, rust is such a bad influence ;__; If you let them say shit theyll rewrite history that rust should replace all other languages, lets waste 10000x programmer time to make the bower checker happy citing perfect mathematical safety. A focus on api design is just a very very different thing from letting safetyphiles masturbate about writing logic engines in the middle of your programming language. ` static analysis` necessarily implies the latter to me, while good api's mostly exist on a lib level(d's runtime being big being a separate debate) and it has to survive being taught too people. *You'll repeat the live mistake several times* if you confuse the two.
May 28
On Thu, May 28, 2026 at 11:38:05PM +0000, monkyyy via Digitalmars-d wrote:On Thursday, 28 May 2026 at 22:58:33 UTC, H. S. Teoh wrote:You're welcome to redefine your own string type as char[] and see how that goes. It's fine when you're in your own app and you get to dictate how and when things are used. Not so nice when you're writing a library and you can't control what user code does or doesn't do. Then you start having to worry about aliasing and stuff getting modified that isn't supposed to be.Remember, string = immutable(char)[]. That "tiny" decision to stick "immutable" on it is what makes D strings so straightforward to use.I cast that away anytime it gets in my way, the biggest thing is that the os is picky about me modifying strings from the executable, the run time couldve just handled that case and char[] wouldve been better for meSure, be my guest. Let me know how it goes. We could have a competition, whoever gets the most segfaults per LOC wins. :-PRemember back in the day when somebody had the bright idea to make AA's more "user-friendly" by allowing const or even mutable arrays as keys?no I dont, lets allow mutable keys :)You're missing the point. C++ iterators, frankly, stink. I used to write a fair amount of C++, and iterators just suck so hard compared to D ranges. They're essentially thinly veiled disguises of pointer bumping, no different from having to write a thousand for-loops and manually bumping the loop counter (and suffering from tons of off-by-1 errors). D ranges are different; by sacrificing some amount of expressiveness (which we got criticized for by C++ folks), we have a much cleaner, robust abstraction that lets us express iteration in a nice, non-leaky way. Now I'm not saying D ranges are perfect -- they have their own warts (ask me about transient ranges sometime). But they're a step up from C++ iterators. "We are not the same." :-P [...]The *idea* of ranges elevated our thinking to a whole 'nother level,the *type theory* of iterators; steponov was probaly being too mathy when he defined it and aa copied the hierarchy from stl. But it was a bunch of uml boxes and thinking about some variants of linked lists and he was trying to imagine what could be made to work with c++ operator overloading.You're missing my point. I'm not arguing for Rust-style memory management, we honestly don't need that in D thanks to the GC. What I'm saying is that statically guaranteeing things at compile-time makes a huge difference to the code clarity, maintainability, and frequency of bugs. Take mutable strings, for example. C and C++ both have mutable strings. Which means that every time you get a string argument, you cannot assume it won't be modified afterwards. This is no problem if you don't need it after you return, but if you're storing it in a data structure somewhere, it's a big problem. Some unrelated code later can come modify it later and break your structure's assumptions without you knowing, and good luck finding the problem because the trigger and the location of the fault are widely separated. As a result, most C/C++ code will copy like mad -- that's the only way to ensure things don't change behind your back. Add to this the lack of string length in C, now you're calling strlen() all over the place. Schlemiel would be proud. Of course, once you copy, you need to clean up afterwards. Which means more chances for bugs and forgetting to free stuff, or your dtor being overzealous and freeing stuff it shouldn't, or some genius keeping a reference to the string somewhere longer than he should -- dangling pointer bugs. In D? Thanks to the immutable guarantee, it's just a single pointer assignment. Job done. No needless string copying, no silly strlen() Schlemielisms, GC cleans up after you -- no memory leaks or dangling pointers. We reduced hundreds of CPU instructions into literally the 1 or 2 for the pointer assignment. Huge win. // Then there's the C/C++ culture of programming by convention. Everyone assumes that a const char* means your string won't mutate -- but the language doesn't stop you, and the compiler will barely emit a groan about it (and you can shut it up with a simple switch). All it takes is for ONE function in some obscure module somewhere in a 2M LOC codebase to do the wrong thing (because the guy who cast away const was under pressure from an overdue release deadline and the customer threatened to sue), and your entire project is ruined. Your assumptions are broken, and who knows where the symptoms will manifest. Probably long after the fact in totally unrelated code. Have fun tracking down where in 2M LOC the bug is. Const is just one of many examples. Void pointers are another frequent source of guffaws. Last I checked, the compiler doesn't even require a cast anymore when assigning a void* to a typed pointer. (Decades ago that used to be an error. Guess people got tired of writing `needlessly_long_name_t *p = (needlessly_long_name_t *)voidPtr;` and decided to let things slip.) You can literally assign void* to anything you wish, and code will happily dereference it, the compiler won't bat an eye, and the code will crash spectacularly at runtime. In D, the compiler will complain loudly and refuse to compile the code until you write out the ugly cast in full -- taking responsibility for problems when it crashes later. In safe code, you can't even perform the cast; you have to find a type-safe way of performing the assignment, meaning the compiler can actually check that you didn't screw it up. Static enforcement == win.Besides, segfaults are only a tiny part of what D eliminates by design.**But you linked a pro-rust article and called for static analyis**, and rusts solution to memory management is to make a proof engine. And like no, no, just no. Poor little rikki here thinks d needs some math prover thing, rust is such a bad influence ;__;If you let them say shit theyll rewrite history that rust should replace all other languages, lets waste 10000x programmer time to make the bower checker happy citing perfect mathematical safety.[...] I've heard lots of Rust hype since the day it was inflicted upon the world. Hasn't convinced me yet, I'm still writing D whenever I can. I think the idea behind Rust's static checker is sound -- you *want* something statically provable and enforceable by the compiler. But the way Rust did it is too user-unfriendly; it requires you to jump through too many hoops. Most non-Rust coders can't swallow that. The incentives are all wrong; you're forced to learn to fly an airplane before you can unlock the door to board the plane in the first place. What they should have done is come up with a design where the most obvious way to write code is the right way. Not something where writing the most obvious code elicits pages of complaints about how you did it wrong and you need to shape up. Besides, being *forced* to reason about memory management is exactly the thing I've come to hate about C/C++. I want to focus on my problem domain, dangit, stop getting in my way and telling me I borrowed this pointer wrong or assign that pointer wrong. Let me do my job and solve the danged programming problem first; hire the GC to clean up any messes afterwards, thank you very much. (This doesn't mean I don't care about memory management -- optimizing bottlenecks require you to take over the reins from the GC sometimes, and I'm fine with doing that, and have done it on occasion. In fact I appreciate how D lets me control memory management to the point I can call malloc/free myself if I really wanted to. But I shouldn't be *required* to think about memory management left, right, and center every other line of code I write. That's just ridiculous.) T -- Being forced to write comments actually improves code, because it is easier to fix a crock than to explain it. -- G. Steele
May 28
On Friday, 29 May 2026 at 00:25:53 UTC, H. S. Teoh wrote:You're missing my point. You're missing the point. your structure's assumptions without you knowing, obscure module somewhere in a 2M LOC codebaseVoid pointers are another frequent source of guffaws. Last I checked, the compiler doesn't even require a cast anymore when assigning a void* to a typed pointer.I think the idea behind Rust's static checker is sound -- you *want* something statically provable and enforceable by the compilerIm asserting I write smaller programs then most, without safe, without and debugger, using compiler bugs, void* whenever I want and I do not have memory management issues that rust and c++ smart pointers and allocators and gc debates claim is a hard problem. Because I do one thing: I push the blame to the datastructure where it belongs. When I call opIndex on my data structures I ussally make a small hack to make it return something; then I use that opIndex to make a range. My code is very very unsafe; but I just dont have these issues people keep claiming are very hard to solve around memory. By going in the exact opposite direction from rust.You're missing the point. C++ iterators, frankly, stink. I used to write a fair amount of C++, and iterators just suck so hard compared to D ranges.But they're a step up from C++ iterators.The *type theory* is the identical as far as I remember, only the api is different, and if you go full iterator you would get the same benefits of going full range. Ideally with both you wouldn't even call for/each directly, so the ugliness of c++ for statements wouldnt matter, altho in practice of course you tend to need the escape. The main difference when I look at aa notes on ranges and c++ theory(they are both linked lists based thoerys and the first upgrade is "bidirectional" and "random access" is a ultimate but fragile one), that steponov had less power over the language day 1 so aa got to make a nicer api.(This doesn't mean I don't care about memory management -- optimizing bottlenecks require you to take over the reins from the GC sometimes, and I'm fine with doing that, and have done it on occasion. In fact I appreciate how D lets me control memory management to the point I can call malloc/free myself if I really wanted to. But I shouldn't be *required* to think about memory management left, right, and center every other line of code I write. That's just ridiculous.)You should care even less.
May 28
On Fri, May 29, 2026 at 01:11:02AM +0000, monkyyy via Digitalmars-d wrote: [...]Im asserting I write smaller programs then most, without safe, without and debugger, using compiler bugs, void* whenever I want and I do not have memory management issues that rust and c++ smart pointers and allocators and gc debates claim is a hard problem. Because I do one thing: I push the blame to the datastructure where it belongs. When I call opIndex on my data structures I ussally make a small hack to make it return something; then I use that opIndex to make a range.Well, that explains a lot. :-D If you mostly write one-man-show small programs, none of these things are even an issue. Have a dangling pointer? No problem, kill the process, restart from clean slate. Have a memory leak? No problem, the program will exit soon, OS will clean up for you. Memory corruption? No problem, the worst case scenario is the program crashes, OS cleans up, program restarts. I write a fair amount of small D programs as helper utilities, and yeah for those I throw all care to the winds, allocate as much as I want, GC will clean up. Cast size_t to int, cast void* to whatever*, doesn't matter. Get the job done before the boss fires you, good enough. Produce output before the program corrupts itself into a bad state, good enough. But if you're writing a long-running daemon running in the background, or an embedded system doing complex processing that cannot afford to crash, because customer data must not be lost, then these issues become big problems. You cannot afford memory corruption, because it might contain sensitive customer data. You cannot afford dangling pointers, because some bot online might exploit that to run arbitrary code and break into your customer's corporate network. You can't afford wasteful memory usage, because you might run out of memory and crash in the middle of customer's production operations and lose customer data. We are not the same. :-D lolMy code is very very unsafe; but I just dont have these issues people keep claiming are very hard to solve around memory. By going in the exact opposite direction from rust.In your use case, no kidding, Rust is total overkill. It's like detonating a nuclear warhead to kill an ant. Completely unnecessary, and doesn't make sense economically.Semantically C++ iterators are actually more powerful than D's. Ergonomically? D ranges win hands down. Cleaner API, less weird edge cases, less likelihood of wrong usage, less boilerplate needed to get useful things done, etc.. [...]You're missing the point. C++ iterators, frankly, stink. I used to write a fair amount of C++, and iterators just suck so hard compared to D ranges.But they're a step up from C++ iterators.The *type theory* is the identical as far as I remember, only the api is different, and if you go full iterator you would get the same benefits of going full range.lol for my one-off shell-script replacement D programs, yeah totally, I don't even think about memory management or type safety. As long as it works and produces the right output, who cares how it got there. The OS cleans up the dirty laundry afterwards anyway, no big deal. Different story when I'm writing a window manager. Doing something stupid like an unsafe pointer cast can bring down my entire work session. Not fun. Or when I'm writing compute-intensive complex computations. Bad memory management / bad or lack of optimization can mean the difference between getting the answer 2 hours later, vs. 2 *months* later. Big difference. (Or in worse cases 2 years later. Or 20 years if you're stupid about exponential algorithms. Or maybe 200 years if you're *really* stupid about it. Beware Shlemiel.) T -- Fact is stranger than fiction.(This doesn't mean I don't care about memory management -- optimizing bottlenecks require you to take over the reins from the GC sometimes, and I'm fine with doing that, and have done it on occasion. In fact I appreciate how D lets me control memory management to the point I can call malloc/free myself if I really wanted to. But I shouldn't be *required* to think about memory management left, right, and center every other line of code I write. That's just ridiculous.)You should care even less.
May 28
On Friday, 29 May 2026 at 02:07:39 UTC, H. S. Teoh wrote:On Fri, May 29, 2026 at 01:11:02AM +0000, monkyyy via Digitalmars-d wrote: [...]Im asserting I write smaller programs then most, without safe, without and debugger, using compiler bugs, void* whenever I want and I do not have memory management issues that rust and c++ smart pointers and allocators and gc debates claim is a hard problem. Because I do one thing: I push the blame to the datastructure where it belongs. When I call opIndex on my data structures I ussally make a small hack to make it return something; then I use that opIndex to make a range.Well, that explains a lot. :-D If you mostly write one-man-show small programs, none of these things are even an issue. Have a dangling pointer? No problem, kill the process, restart from clean slate. Have a memory leak? No problem, the program will exit soon, OS will clean up for you. Memory corruption? No problem, the worst case scenario is the program crashes, OS cleans up, program restarts. I write a fair amount of small D programs as helper utilities, and yeah for those I throw all care to the winds, allocate as much as I want, GC will clean up. Cast size_t to int, cast void* to whatever*, doesn't matter. Get the job done before the boss fires you, good enough. Produce output before the program corrupts itself into a bad state, good enough. But if you're writing a long-running daemon running in the background, or an embedded system doing complex processing that cannot afford to crash, because customer data must not be lost, then these issues become big problems. You cannot afford memory corruption, because it might contain sensitive customer data. You cannot afford dangling pointers, because some bot online might exploit that to run arbitrary code and break into your customer's corporate network. You can't afford wasteful memory usage, because you might run out of memory and crash in the middle of customer's production operations and lose customer data. We are not the same. :-D lolgc os cleans uphttps://monkyyy.itch.io/frut-gam3 video games are the hardest programming even if high frequency stock trading might be "more important" (stupid problems to have) this was forced betterc shit and a not actually support platform Compiler bugs were used to in-executable debugger sub 200 lines of code for the main executable(lib code was of course much bigger) and im telling you there just isnt a memory leak in there for very very **dumb** reasons that I can and do explain to novices, if only anyone would listen I struggle with big projects but line counts and stupid problems to have are not a measurement of success.
May 28
On Friday, 29 May 2026 at 02:51:28 UTC, monkyyy wrote:https://monkyyy.itch.io/frut-gam3 video games are the hardest programming even if high frequency stock trading might be "more important" (stupid problems to have) this was forced betterc shit and a not actually support platform Compiler bugs were used to in-executable debugger sub 200 lines of code for the main executable(lib code was of course much bigger) and im telling you there just isnt a memory leak in there for very very **dumb** reasons that I can and do explain to novices, if only anyone would listen I struggle with big projects but line counts and stupid problems to have are not a measurement of success.Nice music. Relaxing."BetterC is so good." - Aristotle
May 28
On Fri, May 29, 2026 at 02:51:28AM +0000, monkyyy via Digitalmars-d wrote: [...]I struggle with big projects but line counts and stupid problems to have are not a measurement of success.Big projects are not my choice. My boss assigned me to work on a 2M LOC codebase consisting of an entire OS with a large suite of daemons, databases, and other modules running on embedded hardware. A lot of the code is written in C, a significant part of which is legacy code from 20+ years ago when coding standards were (very) different. Hacks like casting (the same!) void* into multiple, incompatible structs is a thing. As is casting a struct pointer into a char* and writing raw bytes into it. And passing around variadic struct pointers and writing past the end (because the caller is assumed to have allocated enough space for it -- and no, the allocated size isn't told to the callee). And Schlemiel string manipulation everywhere (I recently was assigned to clean up after some of his work -- nice guy, but very inefficient :-P). I don't know what success means in this context. It pays the bills, I'm not complaining. End of story. Beauty in engineering is something that only matters when I'm writing D. :-P T -- I am not young enough to know everything. -- Oscar Wilde
May 29
On 5/28/2026 5:25 PM, H. S. Teoh wrote:You're missing the point. C++ iterators, frankly, stink.The fatal flaw with C++ iterators is you always need two pieces of data, one for the beginning and one for the end. They are only connected with each other by the programmer keep track of what goes with what, they are not combined. Another way to look at it as C++ iterators are an abstraction of pointers, while D ranges are an abstraction of arrays.which we got criticized for by C++ folksThe only valid criticism they came up with is the rotate() algorithm cannot be done easily with ranges, while it fits well with iterators. I use ranges for everything, and I can live with a funky implementation of rotate() as a tradeoff. Especially since I never use rotate(). And besides, nothing prevents you from writing an iterator in D.
May 29
On Friday, 29 May 2026 at 18:18:45 UTC, Walter Bright wrote:Another way to look at it as C++ iterators are an abstraction of pointers, while D ranges are an abstraction of arrays.if that was the goal, the lack of indexing is damning They are more like slices, random access moves with the head, ownership is a debatable question, etc.
May 29
On 5/28/2026 5:25 PM, H. S. Teoh wrote:They're essentially thinly veiled disguises of pointer bumping, no different from having to write a thousand for-loops and manually bumping the loop counter (and suffering from tons of off-by-1 errors).Reminds me of this C loop: ```C #include <stdbool.h> #include <stdio.h> typedef long T; bool find(T *array, size_t dim, T t) { int i; for (i = 0; i <= dim; i++); { int v = array[i]; if (v == t) return true; } } ``` which has 5 disastrous bugs in it.
May 29









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