D (Programmiersprache)

aus Wikipedia, der freien Enzyklopädie
D
D Programming Language logo.svg
Basisdaten
Paradigmen: imperativ, objektorientiert, funktional, parallel, generisch, modular
Erscheinungsjahr: 2007
Entwickler: Walter Bright,
Andrei Alexandrescu (ab D 2.0)
Aktuelle Version: 2.100.2  (10. September 2021[1])
Typisierung: Stark, statisch
Wichtige Implementierungen: DMD, GDC, LDC
Beeinflusst von: C, C++, Eiffel, Java, C#, Python, Ruby[2]
Beeinflusste: DScript, Genie, MiniD, Qore, Swift, Vala
Betriebssystem: Plattformunabhängig
Lizenz: Boost Software License[3][4][5][6]
dlang.org

D ist eine seit 1999 von Walter Bright entwickelte Programmiersprache mit objektorientierten, imperativen sowie deklarativen Sprachelementen. D wurde am 3. Januar 2007 in der stabilen Version 1.0 veröffentlicht.[7] Sie lehnt sich äußerlich stark an C++ an, jedoch ohne vollständige sprachliche Kompatibilität dazu.

Vom 17. Juni 2007 bis Ende 2010 wurde die Version 2 von D entwickelt, die neue Funktionalität wie Closures und dem Begriff referentiell transparenter Funktionen sowie das funktionale Paradigma ergänzt, aber nicht kompatibel zum Vorgänger ist. Mit der Freigabe der neuen Version wurde auch das Buch The D Programming Language von Andrei Alexandrescu veröffentlicht, der entscheidend beim Design der Sprache mitwirkte.

Sprachmittel

D übernimmt die meisten Sprachmittel der Sprache C, verzichtet im Gegensatz zu C++ aber auf die Kompatibilität dazu. Dadurch soll die Übernahme von Entwurfsnachteilen vermieden werden. Durch ABI-Kompatibilität sind aber trotzdem alle in C geschriebenen Programme und Bibliotheken nutzbar. Die Anbindung von C++-Code unterliegt dagegen Einschränkungen.[8]

D ist eine objektorientierte, imperative Programmiersprache, die ab D 2.0 auch Möglichkeiten der funktionalen Programmierung innerhalb einer imperativen Programmiersprache bietet, und verfügt über Klassenvorlagen und überladbare Operatoren. D bietet Design by contract und Module. Automatische Speicherbereinigung ist im Gegensatz zu z. B. C/C++ fester, wenn auch prinzipiell optionaler Bestandteil der Sprache, wobei geplant ist, die Laufzeitumgebung selbst insoweit von dieser zu entkoppeln, dass ein Arbeiten mit D auch in Situationen möglich ist, wo der Einsatz eines Garbage Collectors unmöglich oder unerwünscht ist.[9][10] Automatische Referenzzählung wird hingegen nicht unterstützt.[11]

Programme können in D ohne Zeiger geschrieben werden. So bestehen Felder transparent sowohl aus dem Ort ihrer Daten als auch aus ihrer Länge, wodurch Zeigerarithmetik überflüssig ist und die Zulässigkeit von Feldzugriffen zur Laufzeit überprüft werden kann. Im Gegensatz zu Java ist es aber dennoch möglich, Zeiger bei Bedarf nahezu wie in C zu benutzen und so maschinennah zu programmieren.

Konstante und schreibgeschützte Werte

In D werden schreibgeschützte Werte mit const-Datentypen ausgezeichnet. Schreibgeschützt heißt, dass über diese Variable der Wert nicht verändert werden kann. Das bedeutet jedoch nicht, dass es keine andere Variable gibt, über die eine Änderung stattfinden kann. Dem gegenüber stehen (wirklich) konstante Werte, die mit immutable-Datentypen ausgezeichnet werden. Ist ein Wert immutable, gibt es überhaupt keine schreibbare Variable auf den Wert.

Funktionsattribute

In D werden Funktionen mit Funktionsattributen versehen, um anzuzeigen, dass die entsprechende Funktion gewisse Garantien macht (oder auch nicht). Folgende Attribute stellen Garantien dar:

  • pure zeigt an, dass die Funktion referentiell transparent ist.
  • nothrow zeigt an, dass die Funktion keine Exception wirft (möglicherweise aber einen Error).
  • @nogc zeigt an, dass die Funktion keinen Speicher von der automatischen Speicherverwaltung anfordert und daher keine automatische Speicherbereinigung (Garbage Collection) auslösen kann.
  • @safe zeigt an, dass die Funktion keine Speicherzugriffsverletzung auslösen kann, die zu undefiniertem Verhalten führt.[12]
  • @live zeigt an, dass die Funktion das Ownership-Borrowing-Modell für die Speicherverwaltung verwendet.[13]

Zu @safe gehören die Attribute @system und @trusted. Unter anderem unterliegen mit @safe gekennzeichnete Funktionen der Beschränkung, nur andere solche Funktionen aufrufen zu dürfen, sowie solche mit @trusted. Mit @trusted oder @system gekennzeichnete Funktionen dürfen jedoch beliebige Anweisungen ausführen. Nicht explizit gekennzeichnete Funktionen sind implizit @system.

Normalerweise arbeitet ein Großteil des Programms innerhalb der Grenzen von @safe. Da sich D als Systemprogrammiersprache versteht, wird dem Programmierer nicht die Möglichkeit verwehrt, Anweisungen auszuführen, deren Freiheit von möglichen Speicherzugriffsverletzungen sich der Prüfung durch den Compiler entzieht. Typischerweise ist eine Funktion @system, wenn sie Speicherzugriffsverletzungen auslöst, wenn sie falsch benutzt wird. Mit @trusted ausgezeichnete Funktionen bilden eine Zwischenschicht, die @system Code nutzt, aber auf eine Weise, die bekanntermaßen keine Speicherzugriffsverletzungen auslöst. Das wird vom Compiler jedoch nicht maschinell geprüft und es obliegt dem Programmierer oder der Qualitätssicherung, nachzuweisen, dass der @system korrekt verwendet wird.[14]

Ein Beispiel dafür ist die C-Funktion memcpy, die n Bytes von A nach B kopiert. Ist der Puffer B nicht groß genug, die n Bytes aufzunehmen, ist eine Speicherzugriffsverletzung möglich. Daher ist Anbindung von memcpy in D @system. Ein geeigneter D-Wrapper nimmt zwei sog. Slices an statt zweier Zeiger und einer Anzahl. Slices bestehen aus einem Zeiger und einer Länge; es kann also überprüft werden, ob der annehmende Puffer groß genug ist. Ein Wrapper, der einen geordneten Programmabsturz herbeiführt, darf somit mit @trusted annotiert werden. Bei Testläufen sollten Falschverwendungen des Wrappers sofort ersichtlich werden.

Ein weiteres Beispiel sind Funktionen mit Inline-Assembler-Blöcken, da diese sich grundsätzlich einer Überprüfung durch den Compiler entziehen. Möglicherweise ist jedoch offensichtlich, dass ein solcher Block keine unsicheren Speicheroperationen durchführt. Auch dann ist die Annotation @trusted korrekt und sinnvoll.

Compiler

DMD, der Digital Mars D-Compiler,[15] ist die Referenzimplementierung von Walter Bright und für die x86/x86-64-Versionen von Windows, Linux, macOS und FreeBSD erhältlich.

Die wichtigsten Übersetzer mit alternativen Backends sind der auf GCC aufbauende GDC[16] sowie LDC,[17] das sich auf die Qualitäten von LLVM stützt. Neben der hohen Geschwindigkeit des generierten Maschinencodes ermöglichen diese Backends außerdem die Bedienung von x86-64- und verschiedener anderer Architekturen.

Inzwischen wurden auch zwei Compiler für D in D selbst programmiert: Dil[18] und Dang,[19] die kompatibel zu LLVM sind. Ein Codegenerator für die .NET-Plattform stellt eher einen Machbarkeitsnachweis dar als einen funktionstüchtigen Übersetzer.[20] Seit Version 9.1 des GCC wird die Sprache D unterstützt.[21]

Entwicklungsumgebungen

D wird zunehmend von verschiedenen IDEs unterstützt. Zum Einsatz kommen unter anderen die Editoren Entice Designer, Emacs, Vim, Scite, Scite4D, Smultron, TextMate, Zeus, Geany und Visual Studio Code. Vim und Scite4D unterstützen Syntaxhervorhebung und Autovervollständigung. Für TextMate und Visual Studio Code existiert jeweils eine Erweiterung, auch Code::Blocks unterstützt D teilweise.

Darüber hinaus gibt es noch Plug-ins für andere IDEs: Eclipse unterstützt D mit dem Plug-in DDT,[22] für MonoDevelop gibt es Mono-D.[23]

Zudem gibt es in D geschriebene IDEs, wie Poseidon, das Autovervollständigung sowie Refactoring unterstützt und einen integrierten Debugger hat. WinDbg und der GNU Debugger unterstützen D rudimentär.[24][25]

Programmbeispiele

Hallo Welt
Ausgabe der Kommandozeilenparameter
// Programm, geschrieben in D2, das seine Parameter ausgibt
void main(string[] args) @safe
{
    // Importiert writefln aus dem Modul std.stdio
    import std.stdio : writefln;
    // In D können Imports auch in Funktionen stattfinden.

    // Jeder Eintrag im Feld args wird ausgegeben.
    foreach(i, arg; args)
        writefln!"Parameter %d = '%s'"(i, arg);
    // Der Format-String wird zur Compilezeit geprüft,
    // sodass nur passende Parameter verwendet werden können.
    // Werden i und arg vertauscht, kompiliert das Programm nicht.
}

Die Annotation @safe bedeutet, dass sich die Funktion frei von Speicherzugriffsverletzungen ist und der Compiler das überprüfen wird. Sie ist nicht notwendig, aber sinnvoll.

Der main-Funktion werden die Kommandozeilenparameter als ein Feld von Zeichenketten (Strings) übergeben. Ruft man dieses Programm unter Windows mit beispiel.exe -win -s auf, dann gibt es diesen Text in einem Konsolen-Fenster aus:

Parameter 0 = 'beispiel.exe'
Parameter 1 = '-win'
Parameter 2 = '-s'
Unittests

Unittests bzw. Modultests[26] sind Blöcke, die Programmcode enthalten, welcher eine Funktion auf unterschiedliche Fälle testen soll. Zusätzlich kann der in Unittest-Code als Beispiel in der von DDOC[27] generierten Dokumentation aufgeführt werden. In D werden die Unittests unmittelbar vor der Main-Funktion ausgeführt.

/++
    Ermittelt, ob eine Zahl eine Primzahl ist.
    Hinweis: Das Verfahren ist nicht optimal.
Params:
    a: die zu testende Zahl
Returns:
    true, falls die Zahl eine Primzahl ist;
    false, ansonsten
+/
bool isPrime(long a) pure
{
    if (a <= 1)
        return false;

    // falls a keine Primzahl ist und a restlos durch n teilbar ist
    for (long n = 2; n*n <= a; ++n)
        if (a % n == 0)
            return false;

    // a war nicht teilbar -> a muss eine Primzahl sein
    return true;
}

unittest
{
    // Die Bedingung in dem Assert-Aufruf muss erfüllt werden (wahr sein).
    // Sonst ist ein Fehler aufgetreten und das Programm bricht ab.
    assert(isPrime( 0) == false);
    assert(isPrime( 1) == false);
    assert(isPrime( 2) == true);
    assert(isPrime( 7) == true);
    assert(isPrime( 4) == false);
    assert(isPrime(10) == false);

    // Hier wird erwartet, dass der Test fehlschlägt, da 11 prim ist.
    // Fehlerhafter Code: Assert schlägt immer fehl!
    assert(isPrime(11) == false);
}

Die Annotation pure bedeutet, dass isPrime eine referentiell transparente Funktion ist, ihr Ergebnis also nur von den Eingaben, aber nicht von globalen Variablen abhängt. Der Compiler überprüft die Korrektheit der Annotation.

Unittests sind Testfunktionen, die das Verhalten einer Funktion auf alle Möglichkeiten testen sollen. Damit Unittests ausgeführt werden, muss ein Compiler-Flag gesetzt werden (-unittest bei DMD)

Generische Programmierung

Zusätzlich zu Mixin-Templates[28] implementiert die Programmiersprache D alle gängigen Templatearten.[29]

Funktionstemplates
// Diese Funktion liefert als Return-Wert das Maximum der beiden Parameter
// Der Template Datentyp T wird dabei automatisch (falls möglich) erkannt
T max(T)(T a, T b) {
    if (a < b)
        return b;
    else
        return a;
}

unittest {
    assert(max(2, 3) == 3);
    assert(max('a', 'c') == 'c');
    assert(max(3.1415, 2.61) == 3.1415);
    assert(max("Hallo", "Abc") == "Hallo");
}
Meta-Programmierung
// Berechnet das Ergebnis von basis^exp
template Pow(double basis, uint exp) {
    static if (exp > 0)
        enum Pow = basis * Pow!(basis, exp - 1);
    else
        enum Pow = 1;
}

void main() {
    import std.stdio;

    // Da pow ein Template ist, wird das Ergebnis zur Compilezeit bestimmt
    // und steht zur Laufzeit als Konstante zur Verfügung
    writeln("2.7182^3 = ", Pow!(2.7182, 3));
    writeln("3.1415^5 = ", Pow!(3.1415, 5));
}

Ausgabe:

2.7182^3 = 20.0837
3.1415^5 = 305.975
Compile Time Function Execution

Mit noch während der Kompilierung ausgeführten Funktionen (CTFE) können Initialisierungslisten, Lookup-Tables u. Ä. erstellt[30] und durch sog. Mixins bei der Übersetzung aus Strings generierter D-Code eingebunden werden.[31] Diese beiden Features bieten eine Möglichkeit für die Realisierung domänenspezifischer Sprachen in D.

T sum(T)(T[] elems) {
    T result = T(0);

    foreach (elem; elems)
        result += elem;

    return result;
}

enum arr = [10, 13, 14, 15, 18, 19, 22, 24, 25, 36];

// summeVonArr ist zur Kompilierungszeit bekannt.
enum summeVonArr = sum(arr); // == 196

// Statisches Integer-Array mit der Laenge 196
int[summeVonArr] staticArray;

Die Initialisierung von result mit null ist notwendig, falls T durch einen Fließkomma-Datentyp wie double belegt wird, denn uninitialisierte Fließkommazahlen-Variablen werden mit NaN initialisiert, statt mit null.

Literatur

Weblinks

Einzelnachweise

  1. Change Log: 2.100.2 – D Programming Language
  2. D Programming Language 1.0, Intro. Digital Mars
  3. FAQ – Is D open source?
  4. Lizenz im Compiler-Quellcode
  5. Lizenz der Laufzeitbibliothek
  6. Lizenz der Standardbibliothek
  7. Heise Online: Eleganter programmieren: D ist da, 3. Januar 2007 – 14:52
  8. Interfacing to C++
  9. Garbage Collection. dlang.org, abgerufen am 21. September 2019.
  10. Michael Parker: Don’t Fear the Reaper The D Blog, 20. März 2017, abgerufen am 21. September 2019.
  11. Memory Management. im D Wiki, abgerufen am 21. September 2019.
  12. Function Safety. Abgerufen am 3. Januar 2021 (englisch).
  13. Live Functions. Abgerufen am 3. Januar 2021 (englisch).
  14. SafeD. Abgerufen am 3. Januar 2021 (englisch).
  15. DMD (Digital Mars D): DMD
  16. GDC (GNU D Compiler):GDC
  17. LDC auf GitHub
  18. DIL-Projektseite
  19. Dang-Projektseite
  20. D Compiler for .NET
  21. GCC 9 Release Series Changes, New Features, and Fixes
  22. DDT auf Github
  23. Mono-D
  24. windbg Debugger auf dlang.org, abgerufen am 14. Februar 2019.
  25. Debugging with GDB: D, abgerufen am 13. Februar 2019.
  26. dlang.org-unittests
  27. dlang.org-DDOC
  28. dlang.org-Mixin-Templates
  29. dlang.org-Templates
  30. dlang.org-CTFE
  31. dlang.org-mixins