D (Programmiersprache)
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.
D | |
---|---|
Basisdaten | |
Paradigmen: | imperativ, objektorientiert, funktional, parallel, generisch, modular |
Erscheinungsjahr: | 2007 |
Designer: | Walter Bright |
Entwickler: | Walter Bright, Andrei Alexandrescu (ab D 2.0) |
Aktuelle Version: | 2.095.0 (1. Januar 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 |
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 keineException
wirft (möglicherweise aber einenError
).@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
- Ali Çehreli: Programming in D: Tutorial and Reference. 1. Auflage. CreateSpace, 2015, ISBN 978-1-5150-7460-1.
- Adam D. Ruppe: D Cookbook. 1. Auflage. Packt Publishing, 2014, ISBN 978-1-78328-721-5.
- Andrei Alexandrescu: The D Programming Language. 1. Auflage. Addison-Wesley, 2010, ISBN 978-0-321-63536-5.
- Tobias Wassermann, Christian Speer: Programmieren in D. 1. Auflage. entwickler.press, 2007, ISBN 978-3-939084-69-3.
- Manfred Hansen: D-Buch – Programmiersprache D. 2009.
Weblinks
- Offizielle Website
- Die Programmiersprache D bei Digital Mars
- Visual D D-Integration für Visual Studio 2005, 2008 und 2010 (englisch)
- D.NET D-Compiler für .NET (englisch)
Einzelnachweise
- Change Log: 2.095.0 – D Programming Language
- D Programming Language 1.0, Intro. Digital Mars
- FAQ – Is D open source?
- Lizenz im Compiler-Quellcode
- Lizenz der Laufzeitbibliothek
- Lizenz der Standardbibliothek
- Heise Online: Eleganter programmieren: D ist da, 3. Januar 2007 – 14:52
- Interfacing to C++
- Garbage Collection. dlang.org, abgerufen am 21. September 2019.
- Michael Parker: Don’t Fear the Reaper The D Blog, 20. März 2017, abgerufen am 21. September 2019.
- Memory Management. im D Wiki, abgerufen am 21. September 2019.
- Function Safety. Abgerufen am 3. Januar 2021 (englisch).
- Live Functions. Abgerufen am 3. Januar 2021 (englisch).
- SafeD. Abgerufen am 3. Januar 2021 (englisch).
- DMD (Digital Mars D): DMD
- GDC (GNU D Compiler):GDC
- LDC auf GitHub
- DIL-Projektseite
- Dang-Projektseite
- D Compiler for .NET
- GCC 9 Release Series Changes, New Features, and Fixes
- DDT auf Github
- Mono-D
- windbg Debugger auf dlang.org, abgerufen am 14. Februar 2019.
- Debugging with GDB: D, abgerufen am 13. Februar 2019.
- dlang.org-unittests
- dlang.org-DDOC
- dlang.org-Mixin-Templates
- dlang.org-Templates
- dlang.org-CTFE
- dlang.org-mixins