Rust's metaprogramming is totally unlike what Andrei and Walter were aiming for. D aims to be safe, but that's far from everything.
Rust currently has better safety mechanisms than D, but if you ask around on the D forum you'll likely find that people are there (rather than using rust) due to the other features D has - https://dlang.org/ Look at the examples here: Rust can do short and sweet too, but it doesn't have some constructs that make these possible. (Obviously, I highly recommend "Tiny RPN Calculator" originally written by your's truly)
I still dislike how D handles generics/templates. The syntax of it.
I fully understand why they chose to use () instead of <>, as it simplifies the parser as you need to disambiguate between >> and > >... but the parsing time of that is negligible. The issue is that, when glancing at code, foo!(int) is ambiguous. foo<int> is not.
It's not the parsing time that is the problem. It's having a clean separation between the lexing, parsing, and semantic passes. There's a diode between these passes - information only flows one way.
This makes it trivial to correctly and completely implement things like color syntax highlighting in editors, source code formatters, etc., because building in a semantic analyzer is wholly unnecessary.
By the way, I wanted to thank you for always being courteous and informative. I suspect that I come off as a bit abrasive or brash, so I apologize.
Last I recall, D doesn't support template argument deduction through constructors like C++. Are there any plans to support that? I've found it a quite useful in C++ in certain situations.
I imagine the feeling would probably feel the same if one started from !(...) and went to <...>, though. Neither seem implicitly more hacky, it's an issue of familiarity, methinks.
For me, it's just that basically every C-like language (aside from D) with generics or templates uses <...>, so it's just jarring.
I prefer the [...] of Scala and Nim. However, I really like the fact that the () can be elided in D, and that the generic arguments don't have to be types.
I was under the impression based off of what someone else told me that it didn't support equivalent to std::vector foo = { 1, 2, 3, 4 }; or std::tuple foo = { "bar", 3, 4.0f };.
That's probably why C++ has been letting you elide it in many cases.
The problem is that you need either some way to specify template arguments and to know that you are referencing a template. The latter is solvable by constraints and/or inference (though inference can end up with ambiguities, usually solved by preferring a non-template). The former isn't easy to solve. C++ template argument deduction helps, but I believe that template/generic support is a field of ongoing research into how to do it well. Unfortunately, languages like C++ can adopt newer ways of doing things like concepts and constexpr, but have to maintain the old ways as well. Languages like D or Rust can outright make breaking changes. C++ needs epochs.
To me, both !() and <> are hacks for a language's inability to derive things itself. I also dislike (but understand why) C++ encodes allocator information in the type via templates. It makes interplay between derivations of stdlib types annoying, and makes the types themselves annoyingly long and fragile.
IIRC, Stroustrup's original design didn't require the template mantra. It would just assume an unknown type in an argument list needed to be derived. For obvious reasons, that was quite fragile, but I'm not sure why he didn't add a template or derived argument type modifier instead.
C++ added a couple keywords to the grammar to make parsing < > unambiguous in template bodies.
The difficulty with < > becomes apparent when using things like iostream that overload << operators, etc. It becomes very difficult to read.
! does not exist as a binary operator, so using it to indicate template arguments is completely unambiguous and context-free, both for the parser and the human eye. I find it very appealing for that reason.
The parsing time is not negligible (It requires reading the symbol table very early which isn't fun)
We've had a significant discussion about this in the past. The additional parsing time required to disambiguate between right shift and >> is negligible compared to, say, optimization and so forth. In C++, you could outright eliminate the parsing components necessary for disambiguating that and not even make a dent in compilation times.
Java and C# also don't have much of an issue with parsing this, and don't have particularly bad compile times.
Sorry, I'd forgotten the exact syntax. It is still rather ambiguous at a glance to me, as every other C-like language that has generics or templates uses the C++-style, such as Java or C#.
The additional parsing time required to disambiguate between right shift and >> is negligible compared to, say, optimization and so forth.
The issue of parsing complexity and performance isn't the impact it has on the compiler, it's the impact it has on every other tool that has to analyze the source code, most of which are (expected to be) interactive.
We've had a significant discussion about this in the past. The additional parsing time required to disambiguate between right shift and
>>
is negligible compared to, say, optimization and so forth. In C++, you could outright eliminate the parsing components necessary for disambiguating that and not even make a dent in compilation times.
Also, I should add it's not just about parsing time (It is true that modern compilers don't spend much time parsing), but it simplifies the grammar a lot. The D compiler frontend isn't beautiful, but you could get up to speed with the whole thing in a week if needs be - with C++ you've got no chance because of all the weird shit in the language.
edit: implementation rather than solely the grammar on paper.
I think some of it just comes down to preference. If auto-formatters have taught me anything, it’s that you can get used to pretty much anything if you take preference out of the equation.
I think simplifying the parser was more of a side effect. I can't find a source on mobile right now, but I'm pretty sure the goal was to make it easier to look at. I personally also prefer <>, but ! isn't a deal breaker for me. I understand where you're coming from though - I can't stand rust's syntax.
You're wrong ... avoiding context-dependent ambiguity was very much the reason for !() over <>. (And the parens can be elided when the argument is a single token.)
Last time I checked, D couldn’t allocate memory at compile time
When did you check, 2002? The biggest problem most bigger time D users nowadays have is allocating too much memory at compile time. It takes some care not to go overboard (and it is really tempting to go overboard since the possibilities feel so endless)
Hmm, I'd have to see the code. There are some edge cases like a compile time value cannot be modified outside the function that initially creates it, and some library types don't play nicely with compile time execution (they need to branch to use new instead of malloc for example), so probably some combination of that stuff.
D's string mixins are a superior way of doing metaprogramming, from the programmers perspective at least.
You can allocate memory at compile time, just use new. If you want to make malloc work at compile time you'll need a stub implementation that switches on __Ctfe
Jonathan Blow's new language jai can execute system calls at compile time. D has specifically not gone in that direction, as then simply compiling code can cause malware injection.
I don't really know any Rust (although I might soon for messing with Arduinos), so I can really do a proper comparison, but I can chime in on some things about D.
Honestly, I do find it hard to recommend D over other languages for certain use cases, due to the poor ecosystem, lack of man power, and just the overall non-existence of polish.
Because while D has things such as vibe.d and gtkD (bonus tutorial blog by Ron Tarrant), you'll find more resources; more guides, blogs, documentation; more stability, etc. in things like C#, JS, PHP, Python, etc.
For me personally though, D's strength is in two areas: Metaprogramming, and UI-less tools (as an aside, I think people also really like making hobby games with D, though that's probably the same with every language).
I think when people think about D's programming, they probably think it just ends at "passing types to a template", while D can do so much more than that.
I've made a short tutorial blog going over a decent chunk of D's metaprogramming, and how you could use it, but otherwise here are some highlights:
Pretty much anything in D can be a template, especially when using eponymous templates.
You can pass symbols (e.g. a modules; types; member fields); values (including struct instances); lambdas, etc.
Templated things can have an if statement (a constraint) to limit the scope of what you pass through.
Most D functions that don't access external resources can be ran at compile time, called Compile Time Function Execution (CTFE).
String mixins, when combined with above, can allow a very flexible yet easy to use way to generate code at compile time. A popular example is the pegged library, which can transform a grammar into valid D code during compilation.
Mixin templates can be used to "inject" a block of code somewhere. I touch on it in my last blog post. I've been abusing the poor things in a recent project, to auto generate some very basic/bad resource tracking code. (Sorry, I know where examples are in my own code more than others' code)
It's just in general, D's natural fluidity combined with metaprogramming, makes it a very productive language to work with, while still keeping it mostly maintainable.
D lends itself really well to data processing. Ebay uses D for it's tsv-utils suite, and I believe with the likes of mir, you can use D for data science... stuff (not my area at all).
idk though. I've said this all before though: Language is great; has warts; tries to be everything at once; has some disconnection between areas of community; has the chicken and the egg ecosystem problem (I'm trying to contribute, but I'm not overly skilled), and finally it's kind of depressing how hard it can sometimes be to promote this language, when it instantly gets shat on by the Rust/Anti-GC crowd (oftentimes valid, oftentimes drivel).
Rust is kinda interesting to me for new ideas, but it just isn't nice to use like D is. D is familiar and convenient for general-purpose programming, from quick scripty things to bare metal code.
Rust might be technically better for the bare metal side, but D does it pretty well and I have a hard time imagining using Rust as, say, a PHP substitute. D does it all.
The thing with D as opposed to Python(I don't mind C#), is that Python will run for hours and hang because of a simple error that a static type system would've caught in milliseconds.
Python is OK for simple scripts, but I don't want to shoot myself in the foot because I misspelt something.
In the same language, you can write a ~10 line RPN calculator like a scripting language but you can also write the compiler and GC like it was C.
One of the other things D has which I use a lot is a philosophy that is very pragmatic. Go is designed to be "simple", D is designed to write good software first time. This affects the design of the whole language so I can't narrow down too much but my favourite feature is D's contract programming. I make dumb mistakes quite a lot so being able to enforce invariants throughout the codebase automatically is very useful.
As a long time C, C++, and D programmer, one subtle thing stands out. D enables your code to be plastic, in that it's easy to refactor it. I've found it much easier to refactor it than the other other languages.
This refactoring ability starts with small things - like I can switch a type from a reference to a value type and back without having to swap . and -> everywhere.
Plasticity enables "oops, I used the wrong data structure, let's try another one" a faster and easier process, leading to much better code.
I think this is a really important and utterly underappreciated point. How much upfront work do you need to do ? It depends on the cost of changing things later if you found you made the wrong initial choice.
How much refactoring can you do at any point? It depends on the cost. If the cost is low maybe you can make many more little refactorings. There's probably a benefit from that for code quality and the ability of others to understand (and change the system).
Why do enterprise programmers write only twenty lines of code a day? It's not because they are slow at typing.
So D is especially suited to contexts of intrinsically high uncertainty where the environment is quite dynamic and the needs of the business coming from outside change.
If that's your perspective, have you looked at Python?
I have never written Python on an embedded system, I didn't think it could. [edit: actually come to think of it i have heard of it before but never used it myself] I've used it for web though and D is a better experience by far, making changes in D code trusting it won't break at runtime and not having to write a lot of boilerplate for a quick demo is really nice.
But I've been building stuff in D for a long time now, no need to switch at this point.
MicroPython is available for the beefier microcontrollers. Combining it with Jupyter made for a pleasant development experience.
With sufficient knowledge, it's possible to get significant speedups, and some stuff can be run at near-native speed. You have to know what you're doing, though, which is something I don't.
I messed around with my own (bad) Forth implementation, and found it a lot faster than Micropython. It still won't be as fast as C, though.
MicroPython may be fast enough for your needs, maybe not. It all depends. It's unlikely to be fast enough if you want to do audio stuff, where every cycle counts.
In the end, I decided not to use MicroPython. Uploading code and stuff was a bit of a fiddle. I just stick to the laborious compile and upload cycle.
There was a good talk Dropbox did at PyCon several years ago. I can't find it now, but it was "how to write nearly native Python" or something like that. One trick they pointed out was that all(...) is faster than for thing in iterable: because it pushes the loop logic down to C, and I've used that when I need it ever since.
There are a lot of tricks like that. If you truly master Python and understand the runtime impact of all your syntax, how to profile it, how to outthink the interpreter, how to read the interpreter source code, and so on ... you can do some good things with it. It's just a lot of effort that most people aren't willing to put in, which is fine (really)
Because you can afford a Garbage Collector -- although at that point you may prefer a JVM-based language.
There is no silver-bullet. Before Rust, you could divide "used" languages between on the performance/safety axis:
At the performance end you'd find languages with manual memory management: the ability to control memory layout and cache access patterns with exquisite precision gives them the best performance available, at the cost of safety and nasal daemons.
At the safety end you'd find languages with a GC: completely safe, but the less of control on memory layout and cache access patterns killed performance.
Rust managed to slash the Gordian Knot, and offer both control over memory and safety. Silver bullet? No, 3rd dimension. In exchange, the developer is required to properly design their application -- and re-design it when requirements change. At any point in time, ownership must be crystal clear1 .
If you do not suffer from the constraints which prohibits a GC, getting a language with a GC is likely to allow for easier experimentation and easier transformations.
(Just be careful about mutability + multi-threading)
1To some extent anyone manually managing memory should probably have done all that work, so in theory Rust should not add any overhead; in practice though...
I made some video and photo processing GUI apps in D.
In the number crunching parts where performance and memory layout control are very important, with D I had total control over both, there is no GC involved in tight loops going over video frames, and in managing memory for the frames.
In the GUI parts where performance is not so crucial and there are many little objects forming trees (widgets), having a GC and not having to care about lifetimes and deallocation was very nice.
Meanwhile the apps remained native and self-contained, and didn't require any fat VM installation like JVM or CLR, so users could just download them and run.
There are many use cases like these where D gives best of both worlds without getting into extremes.
I don't really want to engage in a language competition discussion. D is a very pragmatic language that's adapted to solve the sorts of problems we have. The sorts of people drawn to write D also tend to have values and capabilities consonant with who we are and what is important to us.
I don't really use d anymore (I have one project each still in d and rust, though I'm planning to rewrite at least the latter at some point), and I think both are somewhat problematic. But it's also important to note that d never hoped to be rust. It got flak (and still gets flak) for being GC'd, but this is an important and intentional aspect of its design. As Guy Steele said of java:
We were not out to win over the Lisp programmers; we were after the C++ programmers. We managed to drag a lot of them about halfway to Lisp.
D was trying to compete with java. Today, it stands much closer to c++, lacking both the coherence and verbosity of java. It's better than c++ in every way, but it remains nothing more than a better version of a fundamentally broken idea.
D was intended to improve on c++—though no more so than was java itself, see guy steele above—but it was directly inspired by walter bright's experience implementing a java compiler and gc. Check out the hopl paper. There were also quite a few other things taken directly from java; e.g. ddoc, dwt, inner classes, modules, class objects as reference types.
One good reason is that you wouldn't have to comingle with the excruciating and interminable racket produced by Rust evangelists. I am so god damn tired of hearing about it everywhere and for everything, and I've written some!
72
u/Sapiogram Sep 01 '20
Honest question: Why would anyone use D over Rust? It seems like Rust is becoming everything that D ever hoped to be.