I suggest that if a language is easier to implement, then it is likely also easier to understand. Isn't it better to spend time learning to write better programs than language arcana? If a language can capture 90% of the power of C++ with 10% of its complexity, I argue that is a worthwhile tradeoff.
Note: printf is actually not really part of D anyway, but since D provides easy access to C's runtime library, D gets it when needed.
The reason D doesn't change this is for the same reason that integral promotion rules and operator precedence rules were kept the same - to make code that looks the same as in C operate the same. If it had subtly different semantics, it will cause frustratingly subtle bugs.
Java is designed to be write once, run everywhere. D is designed for writing efficient native system apps. Although D and Java share the notion that garbage collection is good and multiple inheritance is bad <g>, their different design goals mean the languages have very different feels.
Sure, all this stuff can be done with libraries, following certain coding disciplines, etc. But you can also do object oriented programming in C (I've seen it done). Isn't it incongruous that something like strings, supported by the simplest BASIC interpreter, requires a very large and complicated infrastructure to support? Just the implementation of a string type in STL is over two thousand lines of code, using every advanced feature of templates. How much confidence can you have that this is all working correctly, how do you fix it if it is not, what do you do with the notoriously inscrutable error messages when there's an error using it, how can you be sure you are using it correctly (so there are no memory leaks, etc.)?
D's implementation of strings is simple and straightforward. There's little doubt how to use it, no worries about memory leaks, error messages are to the point, and it isn't hard to see if it is working as expected or not.
GC isn't that hard to implement, either, unless you're building one of the more advanced ones. But a more advanced one is like building a better optimizer - the language still works 100% correctly even with a simple, basic one. The programming community is better served by multiple implementations competing on quality of code generated rather than by which corners of the spec are implemented at all.
Before the C compiler had an inline assembler, I used external assemblers. There was constant grief because many, many different versions of the assembler were out there, the vendors kept changing the syntax of the assemblers, there were many different bugs in different versions, and even the command line syntax kept changing. What it all meant was that users could not reliably rebuild any code that needed assembler. An inline assembler provided reliability and consistency.
struct Foo { union { int a; int b; } struct { int c; int d; } } void main() { Foo f; printf("Foo.size = %d, a.offset = %d, b.offset = %d, c.offset = %d, d.offset = %d\n", f.size, 0, &f.b - &f.a, &f.c - &f.a, &f.d - &f.a); }
char s[8]; strcpy(s, "foo"); printf("string = '%s'\n", s);Attempting this in D, as in:
char[] s; s = "foo"; printf("string = '%s'\n", s);usually results in garbage being printed, or an access violation. The cause is that in C, strings are terminated by a 0 character. The %s format prints until a 0 is encountered. In D, strings are not 0 terminated, the size is determined by a separate length value. So, strings are printf'd using the %.*s format:
char[] s; s = "foo"; printf("string = '%.*s'\n", s);which will behave as expected. Remember, though, that printf's %.*s will print until the length is reached or a 0 is encountered, so D strings with embedded 0's will only print up to the first 0.
double d; // d is set to double.nanNan's have the interesting property in that whenever a nan is used as an operand in a computation, the result is a nan. Therefore, nan's will propagate and appear in the output whenever a computation made use of one. This implies that a nan appearing in the output is an unambiguous indication of the use of an uninitialized variable.
If 0.0 was used as the default initializer for floating point values, its effect can easily be unnoticed in the output, and so if the default initializer was unintended, the bug may go unrecognized.
The default initializer value is not meant to be a useful value, it is meant to expose bugs. Nan fills that role well.
But surely the compiler can detect and issue an error message for variables used that are not initialized? Most of the time, it can, but not always, and what it can do is dependent on the sophistication of the compiler's internal data flow analysis. Hence, relying on such is unportable and unreliable.
Because of the way CPUs are designed, there is no nan value for integers, so D uses 0 instead. It doesn't have the advantages of error detection that nan has, but at least errors resulting from unintended default initializations will be consistent and therefore more debuggable.