Hacker News new | ask | show | jobs
by hardwaresofton 2757 days ago
I would have liked to hear more about what exactly made D so great -- reading through it it's kind of vague. He mentions generics and finally getting LLVM support working as a target, and being able to use D at a high and low-level but I don't see anything here that's groundbreaking.

Could someone who uses D

Hate to be that guy, but if you could compare it with rust that would be double A++ good... I know the targets are somewhat different but I find that I can write high level rust and low level rust. Also, I get the feeling that you could very easily have used Go for the same purposes (D was around long before Go IIRC so maybe it was just not being present).

8 comments

My experience, which of course is anecdotal, is that the advantage is it's easy to change the algorithms and data structures.

I've maintained C and C++ code bases for decades, and I've found that the first algorithm I tried has stayed there in the code. It gets tweaked, optimized, refactored, but it's the same algorithm and data structure.

With D, when I developed the Warp preprocessor, https://github.com/facebookarchive/warp I continually tried different algorithms & data structures to see which was faster.

One reason it's easier is that in C/C++ the . is used for value field access, while -> for pointer field access. In D, . is used for both. So you can easily switch between a value and a pointer to the value by changing little code.

> In D, . is used for both.

Yes, this is a great feature. Rust also does this, and I attribute substantial portion of ease of refactoring Rust to this feature.

Oddly enough, so does Cython.
> In D, . is used for both

This is an absolute pleasure to work with. Turtles (.) all the way down (pointers, values, modules, etc)!

Go also does this, although I never appreciated how useful it is until you made that point. Thanks for that. Also thanks for writing D, it was great to read through it's documentation, and I've been meaning to play around with it.
Thanks, this was a good explanation of where the likely benefits originate for the use case in the OP!
> In D, . is used for both

Also for module access (as opposed to :: in c++). I feel like d has a lot (too much?) of syntax but this is one place where it manages to cut down on it a bit.

D has significantly less syntax than C++, it barely has more than C. The weirdest syntax to get used to in my experience is the template stuff, most everything else is fairly natural. and even then the template stuff is way less complex (but no less powerful) than C++.
That would be nice. I wonder if C would ever consider adding that. Types are tracked by the compiler so it doesn't seem theoretically impossible.
D feels much more like a 'sane' C successor, dropped from a parallel dimension where C++ could evolve without caring much about breaking its legacy code running the world. It has a few warts, some D v1 leftovers, but overall feels much more coherent than modern C++; even as the latter has grown much closer to D by piling more on top of its templating system.

What I personally found the stand out feature of D to be, is its compile-time template/macro system. D was designed by a compiler builder, and it shows. Its compile-time meta-programming (generics/templates) was built on compiler reflection, whereas C++ did it by abusing template instantiation. I bet every modern C++ programmer that writes his first "if __traits(compiles, S.s1) ..." gets a huge grin on his face, remembering the C++ SNIFAE abuse needed. A crude analogy would be working with Lisp macros after years of C preprocessor hacking.

In short, I would recommend D for projects where you want better rapid prototyping and feature turn-around than C++, but the end result needs still to be C-style speed.

If I may be blunt, I could hardly believe SFINAE was a feature, and hated implementing it.

But one thing I liked about C++ was the partial ordering scheme for template selection. I liked it so much D uses it for both templates and function overload selection (as opposed to the multi-level argument matching scheme C++ uses). Once I figured it out, I thought it was a beauty.

Thank you for keeping a unified dispatch/selection algorithm in D! I feel that in language design, simplifying/streamlining the programmer's mental model is often undervalued compared to "but you would expect X to happen in this situation". Case in point, operator precedence hierarchy compared to uniform application a la Lisp and Smalltalk. I personally gladly accept some up-front weirdness if it gives me long-term uniformity.

PS. I also had a pleasant time when I finally grokked Lisp's CLOS generic method partial ordering. It even gives you the ordering on demand as part of the meta-object protocol: https://clisp.sourceforge.io/impnotes/mop-gf.html#gf-argumen...

Do you mind giving an example for the specific use of "c++ partial ordering scheme for template selection"?
https://github.com/MicrosoftDocs/cpp-docs/blob/master/docs/c...

> Multiple function templates that match the argument list of a function call can be available. C++ defines a partial ordering of function templates to specify which function should be called. The ordering is partial because there can be some templates that are considered equally specialized.

> The compiler chooses the most specialized template function available from the possible matches.

And it goes on to give an example.

Essentially, it is the algorithm by which the C++ compiler selects what template specialization to use at your 'call' site. Function templates can be overloaded and essentially create an entire family of specializations. As a C++ programmer, you either modify+compile+run until you get your desired call/specialization; or you take the plunge once your start to heavily rely on meta-template magic and try to grok the ordering scheme defined by the standard.

Crucially, this is completely different from say the way function overload resolution happens in C++. Fun stuff happens in your brain when you try combining different types of selection/overloading (ever tried combining template and regular function overloading on a templated class method?).

Compare ADL (https://en.cppreference.com/w/cpp/language/adl), overload resolution (https://en.cppreference.com/w/cpp/language/overload_resoluti...) and function template overloading (https://en.cppreference.com/w/cpp/language/function_template...)

I like D, but I'd draw different conclusions. D 1.0 was kind of like a "native C#" for me - a language that combines performance, pointers, direct access and ease of garbage collection, batteries included standard library. I loved it. D 2.0 though, I have more mixed feelings about.

It's trying to be a better C++, but in an effort to woo C++ programmers it's slowly becoming C++ itself. D's curse and blessing is that it tries to cover all bases. Safe programming? Kind of supported. OOP programming? Kind of supported. Functional programming? Kind of supported. GC? Kind of supported. No GC? Kind of supported. Everything is supported to some extent, but it takes some effort to learn what exactly is supported in which subset, what language features don't interact well with each other. It's a big boost for expert D programmers, but for less expert ones it's a lot of mental overhead to worry about.

If a function is pure, it is automatically inferred by the compiler and it will be marked for purity (sorry for the lack of words). We don't have to annotate with pure attribute. Also D offers the right balance of functional programming that I can think of. Can you elaborate on what's missing from that point?
Being able to mark a function as pure means the compiler will check it for you - it's a guarantee. Inferring purity is useful, but it is not visible if it is actually pure or not.
Not to forget, C++ template meta-programming was discovered by accident. That's probably the only feature (and C++ is the only language) that I can think of in any language that's discovered by accident and is of utmost essential for introspection capabilities.
> Not to forget, C++ template meta-programming was discovered by accident.

yep, in 1993 some graybeard working on GCC checked files by last modification date to do some refactoring and saw one that hadn't changed since july 7, 1926, aptly called "alexandrescu.c". When he opened it, his computer started glowing yellow and his BSD prompt suddenly changed from '$' to '>'. He executed the testcase associated and the horror happened... a 666 kilobyte dump of template error was uploaded right into comp.lang.c++, which blew the poor hacker's 486DX away. Since then templates roam free again in our universe.

OR

when people say that it was discovered, it's that it was discovered during the process of designing templates and they found it nice, so they kept it.

Brilliant!
> Not to forget, C++ template meta-programming was discovered by accident

That's a bit of an uncharitable reading, no? For others that would like to make up their own mind: https://softwareengineering.stackexchange.com/questions/1253...

lisp was kind of a set of accidents too. remember Mexp ?
It's usually described as a C++ successor, rather than a C successor.

There was a C successor that's been in progress lately, saw it on HN a few months back, I forget the name of it.

EDIT: Was thinking of Zig

> It's usually described as a C++ successor, rather than a C successor.

It is, and I honestly don't see that at all. I interpret "successor" to be more than just an alternative. D is, more or less, a superset of C - there are some small differences, but you can do much of your port from C to D by simply changing the file extension from .c to .d. Heck, you can even include C header files directly in a D program. Although D does most of the same things as C++, it does them in very different ways.

D is a work of love by WalterBright and the community. I like to think of D as a true successor to C++ when people compare Go to a C++ alternative. It is between Rust and Go but it doesnt do away with Object Oriented programming or the many paradigmns that exist for different programming approaches. I usually tell people if you can grasp C, C++, C# or Java you are going to understand plenty of Go.

Rust has a larger learning curve, Go gains plenty of traction over this simple fact.

You hear it often enough "I wanted to learn Rust but Go was simpler to get into" but if those same people gave D a shot they might never look back. D is best understood once you start hacking away at D code because you will appreciate the beauty in D. Different people love different things about it. I love that it is compiled and dont get phased by the optional GC, it doesnt stop Java, C#, or Go from being awesome...

I think the optional part might have a bit to do with it. In Java, C#, Go you are 'stuck' with the garbage collector, so people learned to live with it and usually realized it's not as bad as people claim it to be.
There's a few other subtle things I like about D too, like the constructor[0] for a class is just this() {} instead of SuperLongClassNameFactoryShopPlaceThing () {} so instead of being redundant with typing, you realize the constructor can just have a much more obvious name such as "this", I know Python has __init__ and the @classmethod decorator for the case where you have to 'overload' the constructor. There's also 'auto' which is similar to 'var' in C#. If you are instantiating a class, why the hell do you need to write it twice, shouldn't the compiler know already the second you invoke the class constructor that your variable will be of that type? So 'auto' is nice too. These are seemingly minor things, but being a programmer who loves programming and programming languages I appreciate language aesthetics / syntatic sugar.

Also parallel compilation is my absolute favorite. When I found that compiler flag and used it I was amazed at how fast code compiles with the D compiler.

[0]: https://dlang.org/spec/class.html#constructors

Coincidentally I started reading on D last weekend. There are lots of things that are appealing to me (at least in theory since I haven't used them): UFCS, modules, pure, @safe, contracts, support for "D-scripts" using shebang, etc.

I just need a project and some time to play around with it, but my first impression as someone that worked with C++ and Python in the past is really positive.

Yeah I'm mostly coming from C# / Python so I absolutely love D. I'm not sure what kind of projects you're interested in working on, but if by chance they are web related (or wouldn't mind trying web development) I highly recommend Vibe.d[0] and DiamondMVC[1].

[0]: http://vibed.org/

[1]: https://diamondmvc.org/

Same happened with me and Oberon.

Using an OS written in a GC enabled systems language made me realize that not only it was viable, it was quite productive to work in such environments.

The problem is having a big name OS vendor willing to push it to the nameless crowds no matter what.

So far we have to get happy with half-hearted steps like Swift, ChromeOS/Android(Things) with heavily constrained NDK, .NET/UWP on Windows and maybe in a decade with more baby steps, we are able to reach what Oberon/Topaz/Spin/Inferno/Singularity/Midori had on their design.

I fully agree. I find myself always fighting the Rust or C compilers. Not so with D (or Go for that matter). The GC is very useful as one doesn't have to worry about memory allocation, while also being easy enough work around it by specifying static array lengths etc. Now one can also disable it using the @nogc attribute with nogc functions.
> I would have liked to hear more about what exactly made D so great

This WIP article explains very well the USP of D:

http://erdani.com/hopl2020-draft.pdf (section 3)

This article highlight the value of "design-by-introspection" applied to a particular problem space: https://blog.thecybershadow.net/2014/03/21/functional-image-...

This talk present a concrete example of such: https://www.youtube.com/watch?v=LIb3L4vKZ7U (mind bending stuff)

You might not understand what is new about it, but soon your competitors will understand it.

One of the nice aspects of D is that it interfaces with C++ really nicely. I'm not overly familiar with how Rust handles this, as the last time I tried to glue together Rust and C++ was more than a year ago, and the tooling was in evolution.

From Go / CGo, it can be pretty scary to interface with native C++ code, and it requires lots of annoying boilerplate.

"C++ has a very complex ABI, and the Rust ABI is not frozen. However, both C++ and Rust support functions that use the C ABI. Therefore, interoperability between C++ and Rust involves writing things in such a way that C++ sees Rust code as C code and Rust sees C++ code as C code."

https://hsivonen.fi/modern-cpp-in-rust/

Context: this poster is likely Walter Bright the creator and first implementer of D lang itself.

Cool to know you also read that article about C++ in rust (I personally read a little bit then left because trying to interface with C++ from rust wasn't a problem I had), but I guess it's obvious that you would.

> is likely

Probably 100%

I've written wrappers for C++ libraries to work with D and it's _very_ easy. To work with C++ classes in D, I created factory methods that created/destroyed them with C ABI and used/disposed them respectively.

Working with C is even more easier. There is no FFI, etc. Just use Dstep[1] and get going. Or use dpp[2].

[1] https://github.com/jacob-carlborg/dstep

[2] https://github.com/atilaneves/dpp

Up until quite recently, while the front end of D was written in D, the back end was in C++ (more precisely, C with classes, as it is fairly auld skool).

It's now finally pretty much all in D, and I've been piece by piece refactoring it into more idiomatic D.

Here's a comparison with Nim. In my experience Nim is one of the fastest both in terms of execution and compile time.

https://github.com/timotheecour/D_vs_nim

I see a lot of comparisons between unlike languages (Go and Rust for example) but it looks like D and Nim really are competing for the same niche.
And C# as well, via AOT compilation and the memory management improvements in 7.x versions.
It would be great if someone knowledgeable compared these contenders for about the same space "close to metal":

  * Rust;
  * D;
  * Ada/Spark;
  * Zig;
  
using C and C++ as reference points.

(I also wonder if ATS-lang is used by anyone in practice; it seems to be targeted approximately at the same space.)

Its reflection capabilities are as powerful aa Java if not more so and you can avoid doing so much at runtime using pure functions and templating.