
D is a general purpose systems and applications programming language. It is a high level language, but retains the ability to write high performance code and interface directly with the operating systemAPI's and with hardware. D is well suited to writing medium to large scale million line programs with teams of developers. D is easy to learn, provides many capabilities to aid the programmer, and is well suited to aggressive compiler optimization technology.
D is not a scripting language, nor an interpreted language. It doesn't come with aVM, a religion, or an overriding philosophy. It's a practical language for practical programmers who need to get the job done quickly, reliably, and leave behind maintainable, easy to understand code.
D is the culmination of decades of experience implementing compilers for many diverse languages, and attempting to construct large projects using those languages. D draws inspiration from those other languages (most especially C++) and tempers it with experience and real world practicality.
Why, indeed. Who needs another programming language?
The software industry is continuously evolving at a breakneck pace. New ideas appear, and older ideas are either validated or discarded. What programmers need and want out of a programming language changes. Available memory and computing power have increased by orders of magnitude, as well as the scale of programs being developed.
Compilers are no longer terribly constrained by available computing resources, and so are able to do much more for the programmer. Much more powerful language features have become practical. These features can be difficult to retrofit into existing languages, and when there are enough of these, a new language is justified.
Everything in designing a language is a tradeoff. Keeping some principles in mind will help to make the right decisions.
The general look of D is like C and C++. This makes it easier to learn and port code to D. Transitioning from C/C++ to D should feel natural. The programmer will not have to learn an entirely new way of doing things.
Using D will not mean that the programmer will become restricted to a specialized runtime vm (virtual machine) like the Java vm or the Smalltalk vm. There is no D vm, it's a straightforward compiler that generates linkable object files. D connects to the operating system just like C does. The usual familiar tools likemake will fit right in with D development.
This section lists some of the more interesting features of D in various categories.
D's object oriented nature comes from classes. The inheritance model is single inheritance enhanced with interfaces. The class Object sits at the root of the inheritance hierarchy, so all classes implement a common set of functionality. Classes are instantiated by reference, and so complex code to clean up after exceptions is not required.
See theClasses page for more information.
Classes can be crafted that work with existing operators to extend the type system to support new types. An example would be creating a bignumber class and then overloading the +, -, * and / operators to enable using ordinary algebraic syntax with them.
See theOperator Overloading page for more information.
Functional programming has a lot to offer in terms of encapsulation, concurrent programming, memory safety, and composition. D's support for functional style programming include:
Source files have a one-to-one correspondence with modules.
See theModule page for more information.
Functions and classes are defined once. There is no need for declarations when they are forward referenced. A module can be imported, and all its public declarations become available to the importer.
Example:
class ABC{int func() {return 7; }staticint z = 7;}int q;
All members are defined in the class or struct, not separately.
class Foo{int foo(Bar c) {return c.bar; }}class Bar{int bar() {return 3; }}
Whether a D function is inlined or not is determined by the optimizer settings.
D templates offer a clean way to support generic programming while offering the power of partial specialization. Template classes and template functions are available, along with variadic template arguments and tuples.
See theTemplates page for more information.
Associative arrays are arrays with an arbitrary data type as the index rather than being limited to an integer index. In essence, associated arrays are hash tables. Associative arrays make it easy to build fast, efficient, bug-free symbol tables.
See theAssociative Arrays page for more information.
Documentation has traditionally been done twice - first there are comments documenting what a function does, and then this gets rewritten into a separate html or man page. And naturally, over time, they'll tend to diverge as the code gets updated and the separate documentation doesn't. Being able to generate the requisite polished documentation directly from the comments embedded in the source will not only cut the time in half needed to prepare documentation, it will make it much easier to keep the documentation in sync with the code.Ddoc is the specification for the D documentation generator. This page was generated by Ddoc, too.
Although third party tools exist to do this for other languages. they have some serious shortcomings:
D has the expected support for ordinary functions including global functions, overloaded functions, inlining of functions, member functions, virtual functions, function pointers, etc. In addition:
Functions can be nested within other functions. This is highly useful for code factoring, locality, and function closure techniques.
Anonymous functions can be embedded directly into an expression.
Nested functions and class member functions can be referenced with closures (also called delegates), making generic programming much easier and type safe.
Not only does specifying this help make functions more self-documenting, it eliminates much of the necessity for pointers without sacrificing anything, and it opens up possibilities for more compiler help in finding coding problems.
Such makes it possible for D to directly interface to a wider variety of foreign APIs. There would be no need for workarounds like "Interface Definition Languages".
C arrays have several faults that can be corrected:
int (*array)[3];
In D, the [] for the array go on the left:
int[3]* array;// declares a pointer to an array of 3 intslong[] func(int x);// declares a function returning an array of longs
which is much simpler to understand.
D arrays come in several varieties: pointers, static arrays, dynamic arrays, and associative arrays.
See theArrays page for more information.
String manipulation needs direct support in the language. Modern languages handle string concatenation, copying, etc., and so does D. Strings are a direct consequence of improved array handling.
D uses the concept of a range in lieu of iterators or generators found in other languages. A range is any type that provides a common interface to a sequence of values. The purpose of a range is to allow for a simpler way to write code that works on arbitrary data, thereby making it reusable.
The most basic type of range is called an input range, which provides three methods.
struct MyRange{auto front() {// return the next value in the sequence }void popFront() {// move the front of the sequence to the next value }bool empty() {// true if the range has no more values to return }}
To understand the power of this simple interface, let's run through an example. Say we wanted to write a program that took all of the employees in a company, took out the ones younger than 40, and grouped the remaining into an array of arrays by their organization.
struct Employee{uint id;uint organization_id; string name;uint age;}struct Employees{ Employee[] data;this(Employee[] employees) { data = employees; } Employee front() {return data[0]; }void popFront() { data = data[1 .. $]; }bool empty() {return data.length == 0; }}
Here the data is coming from a constructor as a simple example, but it could come from any source, like a CSV or a database.
Please note that this code should not be used in actual code, because in D, the basic dynamic array also acts as a range, so any algorithm that accepts ranges also accepts arrays. However, static arrays are not considered ranges, as the operationpopFront is based on mutating the length of the range, which is impossible with static arrays. To get a range out of a static array, create a slice containing all of its elements, like so:
int[4] array = [1, 2, 3, 4];// not a rangearray[];// valid range
Now that the range is defined, we can populate it and write the filtering code.
void main(){import std.algorithm.iteration : filter, chunkBy; Employees employees = Employees([ Employee(1, 1,"George", 50), Employee(2, 3,"John", 65), Employee(3, 2,"David", 40), Employee(4, 1,"Eli", 40), Employee(5, 2,"Hal", 35) ]);auto older_employees = employees .filter!(a => a.age > 40)// lambdas in D use the => syntax .chunkBy!((a,b) => a.organization_id == b.organization_id);}
All of the algorithms in std.algorithm work with ranges to avoid the problem of rewriting common functionality for every project. std.algorithm implements sorts, filters, maps, reductions, and more.
Because our Employees struct conforms to the input range definition, it can also be used inforeach loops, which automatically detects if the value passed is an input range.
foreach(employee; employees){ writeln(employee);}which is equivalent to
for(; !employees.empty; employees.popFront()){ writeln(employees.front);}The input range is just the most basic form of range, there are also
Each of these ranges represents a distinct way of accessing the underlying data. Or, in the case of the output range, a way to send data to another source. To learn more about each type of range, see therange primitives page in the standard library documentation.
Each of these types of ranges give you access to different algorithms in the standard library. For example, a filter can be run on an input range, but not a sort, which requires a random access range with the slice operator overload defined. The requirements for each function can be seen in the function signature in the documentation in the template constraints. See thetemplate page and the range primitives link above for more details on template constraints.
As stated before, dynamic arrays and associative arrays in D act as ranges. Specifically, they are random access ranges, so any of the functions in std.algorithm work with these basic types.
The following example uses input ranges and an output range to take data from stdin, take only the unique lines, sort them, and then print the result to stdout.
// Sort linesimport std.stdio;import std.array;import std.algorithm;void main(){ stdin .byLine(KeepTerminator.yes) .uniq .map!(a => a.idup) .array .sort .copy(stdout.lockingTextWriter());
For more examples of range based code, see theRanges chapter in Ali Çehreli's book "Programming In D". Also, see the article Component programming with ranges by H. S. Teoh.
D memory allocation is fully garbage collected. With garbage collection, programming gets much simpler. Garbage collection eliminates the need for tedious, error prone memory allocation tracking code. This not only means much faster development time and lower maintenance costs, but the resulting program frequently runs faster.
For a fuller discussion of this, seegarbage collection.
Despite D being a garbage collected language, the new and delete operations can be overridden for particular classes so that a custom allocator can be used.
RAII is a modern software development technique to manage resource allocation and deallocation. D supports RAII in a controlled, predictable manner that is independent of the garbage collection cycle.
D supports simple C style structs, both for compatibility with C data structures and because they're useful when the full power of classes is overkill.
Device drivers, high performance system applications, embedded systems, and specialized code sometimes need to dip into assembly language to get the job done. While D implementations are not required to implement the inline assembler, it is defined and part of the language. Most assembly code needs can be handled with it, obviating the need for separate assemblers or DLLs.
Many D implementations will also support intrinsic functions analogously to C's support of intrinsics for I/O port manipulation, direct access to special floating point operations, etc.
See theInline Assembler page for more information.
A modern language should do all it can to help the programmer flush out bugs in the code. Help can come in many forms; from making it easy to use more robust techniques, to compiler flagging of obviously incorrect code, to runtime checking.
Contract Programming (invented by Dr. Bertrand Meyer) is a technique to aid in ensuring the correctness of programs. D's version of Contracts includes function preconditions, function postconditions, class invariants, and assert contracts. SeeContracts for D's implementation.
Unit tests can be added to a class, such that they are automatically run upon program startup. This aids in verifying, in every build, that class implementations weren't inadvertently broken. The unit tests form part of the source code for a class. Creating them becomes a natural part of the class development process, as opposed to throwing the finished code over the wall to the testing group.
Unit tests can be done in other languages, but the result is kludgy and the languages just aren't accommodating of the concept. Unit testing is a main feature of D. For library functions it works out great, serving both to guarantee that the functions actually work and to illustrate how to use the functions.
Consider the many library and application code bases out there for download on the web. How much of it comes withany verification tests at all, let alone unit testing? The usual practice is if it compiles, we assume it works. And we wonder if the warnings the compiler spits out in the process are real bugs or just nattering about nits.
Along with Contract Programming, unit testing makes D far and away the best language for writing reliable, robust systems applications. Unit testing also gives us a quick-and-dirty estimate of the quality of some unknown piece of D code dropped in our laps - if it has no unit tests and no contracts, it's unacceptable.
See theUnit Tests page for more information.
Now debug is part of the syntax of the language. The code can be enabled or disabled at compile time, without the use of macros or preprocessing commands. The debug syntax enables a consistent, portable, and understandable recognition that real source code needs to be able to generate both debug compilations and release compilations.
The superiortry-catch-finally model is used rather than just try-catch. There's no need to create dummy objects just to have the destructor implement thefinally semantics.
Multithreaded programming is becoming more and more mainstream, and D provides primitives to build multithreaded programs with. Synchronization can be done at either the method or the object level.
synchronizedint func() { ... }
Synchronized functions allow only one thread at a time to be executing that function.
The synchronize statement puts a mutex around a block of statements, controlling access either by object or globally.
D retains C operators and their precedence rules, order of evaluation rules, and promotion rules. This avoids subtle bugs that might arise from being so used to the way C does things that one has a great deal of trouble finding bugs due to different semantics.
Not only does D have data types that correspond to C types, it provides direct access to C functions. There is no need to write wrapper functions, parameter swizzlers, nor code to copy aggregate members one by one.
Making it possible to interface to any C API or existing C library code. This support includes structs, unions, enums, pointers, and all C99 types. D includes the capability to set the alignment of struct members to ensure compatibility with externally imposed data formats.
D's exception handling mechanism will connect to the way the underlying operating system handles exceptions in an application.
D produces code in standard object file format, enabling the use of standard assemblers, linkers, debuggers, profilers, exe compressors, and other analyzers, as well as linking to code written in other languages.
D provides built-in support for generation of multiple versions of a program from the same text. It replaces the C preprocessor #if/#endif technique.
As code evolves over time, some old library code gets replaced with newer, better versions. The old versions must be available to support legacy code, but they can be marked asdeprecated. Code that uses deprecated versions will be normally flagged as illegal, but would be allowed by a compiler switch. This will make it easy for maintenance programmers to identify any dependence on deprecated features.
/* Sieve of Eratosthenes prime numbers */import std.stdio;void main(){ size_t count;bool[8191] flags; writeln("10 iterations");// using iter as a throwaway variableforeach (iter; 1 .. 11) { count = 0; flags[] = 1;foreach (index, flag; flags) {if (flag) { size_t prime = index + index + 3; size_t k = index + prime;while (k < flags.length) { flags[k] = 0; k += prime; } count += 1; } } } writefln("%d primes", count);}
NB: The expectation may be that array indexx represents the number x, withi + i + 3 seeming odd at first glance.However, if one were to consider each index, it would mean that the first element would represent 0 + 0 + 3 = 3;the second element would represent 1 + 1 + 3 = 5;the third element would represent 2 + 2 + 3 = 7;and so on.So the numbers represented by the array actually go from 3 to (8190 + 8190 + 3), or 16383.