D on Apple silicon basically worked from day 1 (I'm trying to get a blog post about this together, but I don't have access to hardware), via the LLVM D compiler - GCC will apparently get this soon but the Darwin support won't be until the next GCC release (Apparently they got Apple silicon but no help supporting it although I could he wrong about the latter)
There are quite a few ways of approaching D - at it's core it's basically designed to have many obvious features that make life easier, rather than being the XYZ language.
The underlying ethos is really doing obvious things and making the compiler do the work for you - you can generate huge amounts of fast code at compile time. For example, unittests are a first class keyword in D, which makes writing tests easy, therefore people actually write tests. This feature is maybe a 100 line diff to most compilers, and yet basically only D exploits that fact. Similarly with compile time reflection.
You'll have to make your mind up for yourself, but D is proof that the supposed tradeoff between expressiveness and speed is the result of nothing but stockholm syndrome.
> unittests are a first class keyword in D, which makes writing tests easy, therefore people actually write tests. This feature is maybe a 100 line diff to most compilers, and yet basically only D exploits that fact.
I'm interested in how this makes the testing experience much easier. In most of the modern languages I've used setting up tests is a similar amount of words (Elixir, Rust) or one-time fifteen minute task (Kotlin). In all of those there are options for tightly-integrated testing environments, but they're not (entirely) literally in the compiler so you can make your own competing one.
Here's what happens (the same thing happened with Ddoc, the documentation generator build in to the compiler).
Before Ddoc, there were documentation generators like DOxygen, a third party tool. Doxygen was a fine documentation generator, with lots of great features. The trouble was, it was a third party tool:
1. There were other documentation generators, all different.
2. People would install D, and not have a documentation generator. They'd have to do research, pick one, and install it.
3. The third party tool may not work that well with D.
4. The third party tool may be out of sync with the version of D that the user is doing.
5. The third party tool always comes with a large and intimidating manual.
The end result was, maybe 1% of D users used a documentation generator. 10% wrote documentation as a separate file. 89% just wrote zero documentation at all.
Even worse, the 10% documentation rarely matched what the function actually did. In fact, sometimes it was unrecognizable.
This was the state of, for example, the D runtime library documentation. All of it was either totally wrong, out of date, or missing entirely.
Enter Ddoc. Yes, Ddoc is inferior to DOxygen. But every D user now has a documentation already installed, and a perfect match for the version of D they have. Now writing documentation becomes trivial, as Ddoc can read the function headers. When one modifies the function, it's trivial to fix the documentation, as it's right there. When submitting a pull request, the reviewer goes "where's the documentation".
The result was a night and day difference. Suddenly the Phobos documentation was all there, and accurate.
The same effect happened with builtin unittests. Sure, D's builtin unittest framework isn't as good as advanced third party testing frameworks. But it's always there, and always works, and the difference in results is stunning. Take a look at the Phobos source code - half of it is unittests.
It's not about ease at scale but rather friction locally. If you can enforce tests from the top down then all you've saved is some characters, but if you can make testing easier from day 0 people write more tests and get better coverage. There is no setup.
Also, the unit tests the compiler implements are just special functions, the actually running of the tests can be as dumb or as complicated as you want (Which D makes trivial because of the reflection capabilities).
One thing that I don't think you can do with other approaches, is actually have a unit test inside a template, so you know it gets tested for all parameters that it is instantiated with.
> if you can make testing easier from day 0 people write more tests and get better coverage.
I could see it helping move from 0 to nonzero, but most languages have a constant overhead in test setup. If one person starts testing, or you have a standard template, the setup is done.
> Also, the unit tests the compiler implements are just special functions, the actually running of the tests can be as dumb or as complicated as you want (Which D makes trivial because of the reflection capabilities).
Nice! That's basically the approach rust takes (annotate tests with #[test], either use the default test runner that generates a main fn that runs them or bring your own).
> One thing that I don't think you can do with other approaches, is actually have a unit test inside a template, so you know it gets tested for all parameters that it is instantiated with.
That does sound useful. You definitely can't do that in any language I've used. If you need that you definitely have buggy abstractions, but then we always do.
Quite a lot of D's innovative features have become standard in other languages as well. For example, there was an overlooked feature in Ada that I always liked:
int i = 1_000_000;
int phone = 214_222_1234;
I.e. _ can be inserted in numeric literals to make them easier to read. After D did popularized this, this feature started appearing in other languages.
Other such features include ranges, and staticif. Even static assert.
> If you need that you definitely have buggy abstractions, but then we always do.
Not quite, or at least not always. D embraces something called design by introspection - that basically means adapting behaviour at compile time based on what a parameter can do rather than what a parameter should do (in the case of a template). This allows you to write extremely expressive (and fast) code, however you do at this point need to test at a more granular level.
In addition to what other commenters have said, I'll add that D has good portability. It's able to target operating systems and architectures outside the current mainstream.
One thing that makes porting D code easy is byte, short, int and long have the same size across all platforms. It's unbelievable how much time is wasted in C/C++ programming accounting for int not being a reliable size. I know one can use stdint.h, but people still just use `long` and then the code breaks when ported between OSX and Linux.
Another simple but convenient feature is, what if you need the maximum size of an int?
#include <limits.h>
int max = INT_MAX;
But what about:
typedef int myint;
myint mymax = MYINT_MAX; // oops
D has properties, here a .max property:
int max = int.max;
typedef int myint;
myint mymax = myint.max;
Nice things like this eliminate a lot of porting bugs.
Other entries in the recent wave of "compiled high-level" languages also neglect support for operating systems outside the mainstream Linux, macOS, and Windows or ARMv8 and x86_64; D supports a variety of operating systems and targets thanks to its alternative compilers:
EDIT: Short summary (half-subjective) = "More ergonomic + flexible higher-level-syntax C++ that performs just as well."
--------
I'm a programming language enthusiast and enjoy learning + experimenting with them in my free time.
I've written code in most things, especially newer or niche languages. (Always interesting to try them out and get new perspectives)
Anyways, I've quickly grown attached to D, even in comparison to say Rust or Nim, and I could maybe speak to some of those points:
- C/C++ interop: One of only a handful of languages that can do direct C++ interop without a wrapper (others being Ada, Nim, and experimentally Swift).
But it has the most ERGONOMIC interop with C/C++ of any language because of how syntactically similar it is.
- Syntax: If someone were to tell me there was a language that felt like you were writing some kind of very high level Javascript/Typescript/Kotlin/C/C++ mixture, but you still had as much low-level power and access as C++, I would have assumed the language was a joke. That's kind of what D is like -- it's not a joke though!
- Language constructs: D really hit the nail on the head in my mind here.
It has scope guards ("scope(exit)", etc, for methods)
Compile-time function execution, and a metaprogramming/reflection system that blows away most high-level languages I've worked with.
Here's a compile-time generic template that can be added to a struct/class to mix in function pointer definitions from another module or struct/class with reflection:
mixin template InjectFunctionPointers(T)
{
static foreach (fptr; __traits(allMembers, T))
{
static if (isFunctionPointer!(__traits(getMember, T, fptr)))
{
mixin(typeof(__traits(getMember, T, fptr)).stringof, " ", fptr, ";");
}
}
}
struct MyStruct {
// Compile-time embedded into the struct with reflection
InjectFunctionPointers!(module.with.function.pointers);
}
And I'm not sure what the exact term for this is, but it's the equivalent of Rust's "where: T" constraints for methods and C++'s concepts (sort of).
This shows generics/templates, variadic template arguments, and the concept I just mentioned:
long square_root(long x)
in {
assert(x >= 0);
} out (result) {
assert((result * result) <= x
&& (result+1) * (result+1) > x);
} do {
return cast(long)std.math.sqrt(cast(real)x);
}
D has "no-GC" modes and manually memory management if you want. You can just add "@nogc" above a function (or an entire module/program) and it'll disable the garbage collector.
There's also "-betterC" for a stripped down, non-memory managed, C-like version of D without the stdlib.
// Disable the garbage collector for the method
// Or, put @nogc: at the top of a module/program to disable it for the entire thing
@nogc
void main() {
// stuff
}
There's also a somewhat newer Rust-like ownership + borrow-checker system in D, with @live.
Last but not least, the community is small but active and close-knit + welcoming.
-------------------
That being said:
- D has a much smaller ecosystem of tooling and libs than Rust/C++
- Similarly the development experience isn't quite on par. It's not bad though, quite usable, particularly on VS Code (the Visual Studio extension is also solid).
I'm really annoyed when people repeat the part about D having a Rust-like borrow checking system.
This is absolutely something that the project has communicated about, but that communication shouldn't be repeated uncritically.
If you actually look at what @live semantics are like... well, they're quite incomplete, under-specified, and they lack a roadmap for getting to a state where they could offer GC-less memory safety in D.
I have yet to hear of any project successfully using the @live feature in a production environment, and I fully expect the same thing to be true 5 years from now.
The slightly cheeky answer is that the existing features really kill off a huge number of bugs to start with, the more serious answer is that I, for one, am not uncritical in the slightest and will be doing what I can do move the feature forward. @live is a good idea with a slightly shoddy implementation, that made it a good first pass but hard to extend and maintain.
Also, roadmap coming soon. We should've done one earlier this year, but it is the specific focus of the upcoming foundation internal meeting, and we will make it happen.
`scope()` means that it only executes `exit` once the block goes out of scope, but what's afterwards is still "executed" before it?
The website docs seem pretty nice and comprehensive (as in with enough technical details but also examples), and I like the idea of having some higher-level constructs while being able to go full lower.
If I wanted to use a C lib I would need to wrap it right? I can't just use it directly?
Not wrap per se, but bind. The parts of the .h file you need needs to be translated to D. There's no runtime cost to this and the function works the same as in C which is why I don't like calling it a wrapper.
But doing this is easy, sometimes literal copy/paste, often copy/paste with simple adjustments. There's various automated options that can do 95% of the work too.
Ah, so basically you just have to create somehow a header file that D can use? I've looked into the `deimos` repos and there's a bunch of them, what I see is, C header files in a folder, then a folder with one or several d files (not .di), that set up somethings and have a
extern (C) {
...
}
block declaring types and funcs from the C header file. Then when building you just link the original required C libs? It seems nice.
Ok, been to short into D to understand doc nuances, but what it means:
"WARNING: The definition and usefulness of property functions is being reviewed"
Does it meant that it is just language update proposal? Or it's already tested in D implementations?
It's one of the things we want to improve but aren't sure exactly which way to go. It's not a huge issue for D code at the moment, so we're not rushing into it.
D on Apple silicon basically worked from day 1 (I'm trying to get a blog post about this together, but I don't have access to hardware), via the LLVM D compiler - GCC will apparently get this soon but the Darwin support won't be until the next GCC release (Apparently they got Apple silicon but no help supporting it although I could he wrong about the latter)