D (język programowania) - D (programming language)

Język programowania D
Język programowania D logo.svg
Paradygmat Wieloparadygmat : funkcjonalny , imperatywny , obiektowy
Zaprojektowany przez Walter Bright , Andrei Alexandrescu (od 2007)
Deweloper Fundacja Języka D
Po raz pierwszy pojawiły się 8 grudnia 2001 r .; 19 lat temu ( 2001-12-08 )
Wersja stabilna
2.097,0  Edytuj to na Wikidanych / 3 czerwca 2021 ; 4 miesiące temu ( 3 czerwca 2021 )
Dyscyplina pisania Wnioskowane , statyczne , silne
OS FreeBSD , Linux , macOS , Windows
Licencja Zwiększyć
Rozszerzenia nazw plików .D
Strona internetowa dlang .org
Główne wdrożenia
DMD ( implementacja referencyjna ), GCC ,

GDC ,

LDC , SDC
Wpływem
C , C++ , C# , Eiffel , Java , Python
Pod wpływem
Genie , MiniD, Qore , Swift , Vala , C++11 , C++14 , C++17 , C++20 , Go , C# i inne.

D , znany również jako Dlang , to multi-paradygmat język programowania System stworzony przez Walter Bright w cyfrowej Mars i wydany w 2001 roku Andrei Alexandrescu dołączył do projektowania i rozwoju wysiłku w roku 2007. Mimo to powstało jako re-inżynierii C ++ , D to odrębny język. Przeprojektowano niektóre podstawowe funkcje C++, jednocześnie dzieląc cechy innych języków, w szczególności Java , Python , Ruby , C# i Eiffel .

W celach projektowych tego języka próbowano połączyć wydajność i bezpieczeństwo języków kompilowanych z ekspresyjną mocą nowoczesnych języków dynamicznych . Idiomatyczny kod D jest zwykle tak szybki, jak równoważny kod C++, a jednocześnie jest krótszy. Język jako całość nie jest bezpieczny w pamięci, ale zawiera opcjonalne atrybuty przeznaczone do sprawdzania bezpieczeństwa pamięci.

Wpisz wnioskowanie , automatyczne zarządzanie pamięcią i cukier syntaktyczny dla popularnych typów umożliwić szybszy rozwój , podczas gdy granice kontroli , programowanie kontraktowe funkcji i współbieżności -aware typ systemu pomocy zmniejszyć występowanie błędów .

Cechy

D został zaprojektowany na podstawie wniosków wyciągniętych z praktycznego użycia C++, a nie z czysto teoretycznej perspektywy. Chociaż język używa wielu pojęć C i C++, odrzuca również niektóre lub używa różnych podejść (i składni), aby osiągnąć pewne cele. Jako taki, nie jest kompatybilny ze źródłami (ani nie ma być) ogólnie z kodem źródłowym C i C++ (niektóre prostsze bazy kodu z tych języków mogą dzięki szczęściu działać z D lub wymagać pewnego przeportowania ). D został jednak ograniczony w swoim projekcie przez zasadę, że każdy kod, który był legalny zarówno w C, jak i D, powinien zachowywać się w ten sam sposób.

D zyskał pewne cechy przed C++, takie jak domknięcia , funkcje anonimowe , wykonywanie funkcji w czasie kompilacji , zakresy , wbudowane koncepcje iteracji kontenerów i wnioskowanie o typie . D dodaje do funkcjonalności C ++ również poprzez wdrażanie programowanie kontraktowe , testów jednostkowych , prawdziwy modułów , zbieranie śmieci , pierwszej klasy tablice , tablice asocjacyjne , dynamiczne tablice , tablica krojenie , funkcje zagnieżdżone , wartościowanie leniwe , scoped (odroczone) wykonanie kodu, a przeprojektowaną składnię szablonu . Wielokrotne dziedziczenie C++ zostało zastąpione pojedynczym dziedziczeniem w stylu Java z interfejsami i domieszkami . Z drugiej strony, składnia deklaracji, instrukcji i wyrażeń D jest bardzo zbliżona do składni C++.

D zachowuje zdolność C++ do wykonywania programowania niskopoziomowego, w tym wbudowanego asemblera , który typuje różnice między D a językami aplikacji, takimi jak Java i C# . Inline assembler umożliwia programistom wprowadzanie specyficznego dla maszyny kodu asemblera w standardowym kodzie D, metoda używana przez programistów systemowych w celu uzyskania dostępu do niskopoziomowych funkcji procesora potrzebnych do uruchamiania programów, które są bezpośrednio połączone z podstawowym sprzętem , takim jak systemy operacyjne i sterowniki urządzeń , a także pisanie wysokowydajnego kodu (np. przy użyciu rozszerzeń wektorowych, SIMD ), który jest trudny do automatycznego wygenerowania przez kompilator.

D domyślnie obsługuje przeciążanie funkcji i przeciążanie operatorów , a także tablice dynamiczne i tablice asocjacyjne. Symbole (funkcje, zmienne, klasy) można deklarować w dowolnej kolejności - deklaracje forward nie są wymagane. Podobnie importy mogą być wykonywane niemal w dowolnej kolejności, a nawet być ograniczone (tj. importować tylko jakiś moduł lub jego część wewnątrz funkcji, klasy lub testu jednostkowego). D ma wbudowaną obsługę komentarzy do dokumentacji, umożliwiającą automatyczne generowanie dokumentacji .

Paradygmaty programowania

D obsługuje pięć głównych paradygmatów programowania :

Tryb rozkazujący

Programowanie imperatywne w D jest prawie identyczne jak w C. Funkcje, dane, instrukcje, deklaracje i wyrażenia działają tak samo, jak w C, a dostęp do biblioteki wykonawczej C można uzyskać bezpośrednio. Z drugiej strony, niektóre godne uwagi różnice między D i C w obszarze programowania imperatywnego obejmują foreachkonstrukcję pętli D , która umożliwia pętlę po kolekcji, oraz funkcje zagnieżdżone , które są funkcjami zadeklarowanymi wewnątrz innej i mogą uzyskiwać dostęp do zmiennych lokalnych funkcji otaczającej .

import std.stdio;

void main() {
    int multiplier = 10;
    int scaled(int x) { return x * multiplier; }

    foreach (i; 0 .. 10) {
        writefln("Hello, world %d! scaled = %d", i, scaled(i));
    }
}

Zorientowany obiektowo

Programowanie obiektowe w D opiera się na pojedynczej hierarchii dziedziczenia, przy czym wszystkie klasy pochodzą z klasy Object. D nie obsługuje dziedziczenia wielokrotnego; zamiast tego używa interfejsów w stylu Java , które są porównywalne z czystymi abstrakcyjnymi klasami C++, oraz domieszkami , które oddzielają zwykłą funkcjonalność od hierarchii dziedziczenia. D umożliwia również definiowanie metod statycznych i końcowych (niewirtualnych) w interfejsach.

Interfejsy i dziedziczenie w D obsługują typy kowariantne dla zwracanych typów przesłoniętych metod.

D obsługuje przekazywanie typów, a także opcjonalną niestandardową dynamiczną wysyłkę .

Klasy (i interfejsy) w D mogą zawierać niezmienniki, które są automatycznie sprawdzane przed i po wejściu do metod publicznych, zgodnie z metodologią projektowania według umowy .

Wiele aspektów klas (i struktur) można introspekcji automatycznie w czasie kompilacji (forma refleksji przy użyciu type traits) i w czasie wykonywania (RTII / TypeInfo), aby ułatwić kod generyczny lub automatyczne generowanie kodu (zwykle przy użyciu technik czasu kompilacji).

Funkcjonalny

D obsługuje funkcje programowania funkcjonalnego, takie jak literały funkcji , domknięcia , obiekty rekurencyjnie niezmienne i użycie funkcji wyższego rzędu . Istnieją dwie składnie dla funkcji anonimowych, w tym formularz z wieloma instrukcjami i „skrócona” notacja z jednym wyrażeniem:

int function(int) g;
g = (x) { return x * x; }; // longhand
g = (x) => x * x;          // shorthand

Istnieją dwa wbudowane typy literałów funkcji: function, który jest po prostu wskaźnikiem do funkcji przydzielonej na stosie, oraz delegate, który zawiera również wskaźnik do otaczającego środowiska. Wnioskowanie o typie może być używane z funkcją anonimową, w takim przypadku kompilator tworzy, delegatechyba że może udowodnić, że wskaźnik środowiska nie jest konieczny. Podobnie, aby zaimplementować zamknięcie, kompilator umieszcza zamknięte zmienne lokalne na stercie tylko wtedy, gdy jest to konieczne (na przykład, jeśli zamknięcie jest zwracane przez inną funkcję i wychodzi z zakresu tej funkcji). Podczas korzystania z wnioskowania o typie kompilator doda również atrybuty, takie jak purei nothrowdo typu funkcji, jeśli może udowodnić, że mają one zastosowanie.

Inne funkcje funkcjonalne, takie jak currying i popularne funkcje wyższego rzędu, takie jak map , filter i reduction są dostępne za pośrednictwem standardowych modułów bibliotecznych std.functionali std.algorithm.

import std.stdio, std.algorithm, std.range;

void main()
{
    int[] a1 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
    int[] a2 = [6, 7, 8, 9];

    // must be immutable to allow access from inside a pure function
    immutable pivot = 5;

    int mySum(int a, int b) pure nothrow // pure function
    {
        if (b <= pivot) // ref to enclosing-scope
            return a + b;
        else
            return a;
    }

    // passing a delegate (closure)
    auto result = reduce!mySum(chain(a1, a2));
    writeln("Result: ", result); // Result: 15

    // passing a delegate literal
    result = reduce!((a, b) => (b <= pivot) ? a + b : a)(chain(a1, a2));
    writeln("Result: ", result); // Result: 15
}

Alternatywnie powyższe kompozycje funkcji można wyrazić przy użyciu składni Uniform function call (UFCS) w celu uzyskania bardziej naturalnego czytania od lewej do prawej:

    auto result = a1.chain(a2).reduce!mySum();
    writeln("Result: ", result);

    result = a1.chain(a2).reduce!((a, b) => (b <= pivot) ? a + b : a)();
    writeln("Result: ", result);

Równoległość

Koncepcje programowania równoległego są zaimplementowane w bibliotece i nie wymagają dodatkowego wsparcia ze strony kompilatora. Jednak system typu D i kompilator zapewniają, że udostępnianie danych może być wykrywane i zarządzane w sposób przejrzysty.

import std.stdio : writeln;
import std.range : iota;
import std.parallelism : parallel;

void main()
{
    foreach (i; iota(11).parallel) {
        // The body of the foreach loop is executed in parallel for each i
        writeln("processing ", i);
    }
}

iota(11).paralleljest równoznaczne z std.parallelism.parallel(iota(11))użyciem UFCS.

Ten sam moduł obsługuje również, taskPoolktóre mogą być używane do dynamicznego tworzenia zadań równoległych, a także operacji map-filter-reduce i fold style na zakresach (i tablicach), co jest przydatne w połączeniu z operacjami funkcjonalnymi. std.algorithm.mapzwraca leniwie oceniany zakres, a nie tablicę. W ten sposób elementy są automatycznie obliczane przez każde zadanie pracownika równolegle.

import std.stdio : writeln;
import std.algorithm : map;
import std.range : iota;
import std.parallelism : taskPool;

/* On Intel i7-3930X and gdc 9.3.0:
 * 5140ms using std.algorithm.reduce
 * 888ms using std.parallelism.taskPool.reduce
 *
 * On AMD Threadripper 2950X, and gdc 9.3.0:
 * 2864ms using std.algorithm.reduce
 * 95ms using std.parallelism.taskPool.reduce
 */
void main()
{
  auto nums = iota(1.0, 1_000_000_000.0);

  auto x = taskPool.reduce!"a + b"(
      0.0, map!"1.0 / (a * a)"(nums)
  );

  writeln("Sum: ", x);
}

Konkurencja

Współbieżność jest w pełni zaimplementowana w bibliotece i nie wymaga wsparcia ze strony kompilatora. Możliwe są alternatywne implementacje i metodologie pisania współbieżnego kodu. Korzystanie z systemu pisania D pomaga zapewnić bezpieczeństwo pamięci.

import std.stdio, std.concurrency, std.variant;

void foo()
{
    bool cont = true;

    while (cont)
    {
        receive( // Delegates are used to match the message type.
            (int msg) => writeln("int received: ", msg),
            (Tid sender) { cont = false; sender.send(-1); },
            (Variant v) => writeln("huh?") // Variant matches any type
        );
    }
}

void main()
{
    auto tid = spawn(&foo); // spawn a new thread running foo()

    foreach (i; 0 .. 10)
        tid.send(i);   // send some integers

    tid.send(1.0f);    // send a float
    tid.send("hello"); // send a string
    tid.send(thisTid); // send a struct (Tid)

    receive((int x) => writeln("Main thread received message: ", x));
}

Metaprogramowanie

Metaprogramowanie jest obsługiwane przez szablony, wykonywanie funkcji w czasie kompilacji, krotki i domieszki łańcuchów. Poniższe przykłady demonstrują niektóre funkcje czasu kompilacji D.

Szablony w D mogą być napisane w bardziej imperatywnym stylu w porównaniu do funkcjonalnego stylu C++ dla szablonów. Jest to zwykła funkcja, która oblicza silnię liczby:

ulong factorial(ulong n) {
    if (n < 2)
        return 1;
    else
        return n * factorial(n-1);
}

Tutaj static ifzademonstrowano użycie konstrukcji warunkowej czasu kompilacji D w celu skonstruowania szablonu, który wykonuje te same obliczenia przy użyciu kodu, który jest podobny do kodu funkcji powyżej:

template Factorial(ulong n) {
    static if (n < 2)
        enum Factorial = 1;
    else
        enum Factorial = n * Factorial!(n-1);
}

W poniższych dwóch przykładach szablon i funkcja zdefiniowane powyżej są używane do obliczania silni. Typy stałych nie muszą być określane jawnie, ponieważ kompilator wyprowadza ich typy z prawej strony przypisań:

enum fact_7 = Factorial!(7);

To jest przykład wykonania funkcji w czasie kompilacji (CTFE). Zwykłe funkcje mogą być używane w stałych wyrażeniach czasu kompilacji pod warunkiem, że spełniają określone kryteria:

enum fact_9 = factorial(9);

W std.string.formatpełni funkcję printf-jak formatowania danych (także w czasie kompilacji przez CTFE) i „MSG” pragma pokazuje wynik w czasie kompilacji

import std.string : format;
pragma(msg, format("7! = %s", fact_7));
pragma(msg, format("9! = %s", fact_9));

Domieszki ciągów w połączeniu z wykonywaniem funkcji w czasie kompilacji pozwalają na generowanie kodu D przy użyciu operacji na ciągach w czasie kompilacji. Może to służyć do analizowania języków specyficznych dla domeny , które zostaną skompilowane jako część programu:

import FooToD; // hypothetical module which contains a function that parses Foo source code
               // and returns equivalent D code
void main() {
    mixin(fooToD(import("example.foo")));
}

Zarządzanie pamięcią

Pamięć jest zwykle zarządzana za pomocą garbage collection , ale określone obiekty mogą zostać sfinalizowane natychmiast, gdy wyjdą poza zakres. Z tego korzysta większość programów i bibliotek napisanych w D.

W przypadku, gdy potrzebna jest większa kontrola o układ pamięci i lepszą wydajność, jawne zarządzanie pamięcią jest możliwe przy użyciu przeciążone operatory new i delete, wywołując C „s malloc i wolne bezpośrednio lub wdrażania niestandardowych schematów podzielnik (tj na stosie z awaryjnej przydział styl RAII, liczenie referencji, wspólne liczenie referencji). Zbieranie śmieci może być kontrolowane: programiści mogą dodawać i wyłączać zakresy pamięci z obserwowania przez kolektor, mogą wyłączać i włączać kolektor oraz wymuszać generacyjny lub pełny cykl zbierania. Podręcznik podaje wiele przykładów, jak zaimplementować różne wysoce zoptymalizowane schematy zarządzania pamięcią, gdy odśmiecanie pamięci jest nieodpowiednie w programie.

W funkcjach structssą domyślnie alokowane na stosie, podczas gdy classesdomyślnie alokowane na stercie (z odniesieniem tylko do instancji klasy znajdującej się na stosie). Można to jednak zmienić dla klas, na przykład używając standardowej biblioteki szablonu std.typecons.scopedlub używając newfor structs i przypisując wskaźnik zamiast do zmiennej opartej na wartości.

W funkcji statyczne tablice (o znanej wielkości) są alokowane na stosie. W przypadku tablic dynamicznych można użyć core.stdc.stdlib.allocafunkcji (podobnie jak funkcja C alloca, aby alokować pamięć na stosie. Zwrócony wskaźnik może zostać użyty (przekonwertowany) do (wpisanej) tablicy dynamicznej za pomocą wycinka (jednak zmiana rozmiaru tablicy, w tym dołączanie musi być unikane oraz z oczywistych względów nie mogą być zwracane z funkcji).

Słowa scopekluczowego można używać zarówno do adnotowania części kodu, ale także do zmiennych i klas/struktur, aby wskazać, że należy je zniszczyć (wywołać destruktor) natychmiast po wyjściu z zakresu. Niezależnie od tego, jaka pamięć jest zwalniana, zależy również od implementacji i różnic między klasą a strukturą.

std.experimental.allocator zawiera modułowe i komponowalne szablony alokatorów, umożliwiające tworzenie niestandardowych, wysokowydajnych alokatorów dla specjalnych przypadków użycia.

Bezpieczny

SafeD to nazwa nadana podzbiorowi D, która gwarantuje bezpieczeństwo pamięci (brak zapisów w pamięci, która nie została przydzielona lub została poddana recyklingowi). Oznaczone funkcje @safesą sprawdzane w czasie kompilacji, aby upewnić się, że nie używają żadnych funkcji, które mogłyby spowodować uszkodzenie pamięci, takich jak arytmetyka wskaźników i niesprawdzone rzutowania, a wszelkie inne wywoływane funkcje również muszą być oznaczone jako @safelub @trusted. Funkcje można oznaczyć @trusteddla przypadków, w których kompilator nie może odróżnić bezpiecznego użycia funkcji, która jest wyłączona w SafeD, od potencjalnego przypadku uszkodzenia pamięci.

Bezpieczeństwo dożywotnie zakresu

Początkowo pod banerami DIP1000 i DIP25 (obecnie część specyfikacji języka), D zapewnia ochronę przed niektórymi źle sformułowanymi konstrukcjami obejmującymi czas życia danych.

Obecne mechanizmy zajmują się przede wszystkim parametrami funkcji i pamięcią stosu, jednak ambicją lidera języka programowania jest zapewnienie dokładniejszego traktowania czasów życia w języku programowania D. (Pod wpływem pomysłów z języka programowania Rust ).

Dożywotnie bezpieczeństwo zadań

W ramach kodu @safe sprawdzany jest okres istnienia przypisania obejmującego typ referencyjny, aby upewnić się, że okres istnienia cesjonariusza jest dłuższy niż przydzielony.

Na przykład:

@safe void test()
{
    int tmp = 0; // #1
    int* rad;    // #2
    rad = &tmp;  // If the order of the declarations of #1 and #2 is reversed, this fails.
    {
    	int bad = 45; // Lifetime of "bad" only extends to the scope in which it is defined.
        *rad = bad;   // This is kosher.
        rad = &bad;   // Lifetime of rad longer than bad, hence this is not kosher at all.
    }
}

Adnotacje dotyczące żywotności parametru funkcji w ramach @bezpiecznego kodu

W przypadku zastosowania do parametru funkcji, który jest typu wskaźnika lub referencji, słowa kluczowe zwracają i zakres ograniczają czas życia i użycie tego parametru.

Norma dyktuje następujące zachowanie:

Klasa pamięci Zachowanie (i ograniczenia do) parametru z klasą pamięci
zakres nie można zmienić znaczenia odwołań w parametrze. Ignorowane dla parametrów bez odniesień
powrót Parametr może zostać zwrócony lub skopiowany do pierwszego parametru, ale w przeciwnym razie nie będzie uciekał z funkcji. Takie kopie są wymagane, aby nie przeżyły argumentów, z których zostały wyprowadzone. Ignorowane dla parametrów bez odniesień

Przykład z adnotacjami podano poniżej.

@safe:

int* gp;
void thorin(scope int*);
void gloin(int*);
int* balin(return scope int* p, scope int* q, int* r)
{
     gp = p; // error, p escapes to global gp
     gp = q; // error, q escapes to global gp
     gp = r; // ok

     thorin(p); // ok, p does not escape thorin()
     thorin(q); // ok
     thorin(r); // ok

     gloin(p); // error, gloin() escapes p
     gloin(q); // error, gloin() escapes q
     gloin(r); // ok that gloin() escapes r

     return p; // ok
     return q; // error, cannot return 'scope' q
     return r; // ok
}

Interakcja z innymi systemami

C „s interfejs binarny aplikacji (ABI) jest obsługiwany, jak również wszystkich podstawowych i pochodnych typów C, umożliwiając bezpośredni dostęp do istniejącego kodu C i bibliotek. Wiązania D są dostępne dla wielu popularnych bibliotek C. Dodatkowo standardowa biblioteka C jest częścią standardu D.

W systemie Microsoft Windows D może uzyskać dostęp do kodu COM ( Component Object Model ).

Dopóki zarządzanie pamięcią jest odpowiednio załatwione, wiele innych języków można mieszać z D w jednym pliku binarnym. Na przykład kompilator GDC pozwala łączyć ze sobą C, C++ i inne obsługiwane kody językowe. Kod D (funkcje) można również oznaczyć jako używający C, C++, Pascal ABI, a tym samym przekazać do bibliotek napisanych w tych językach jako wywołania zwrotne . Podobnie dane mogą być wymieniane między kodami napisanymi w tych językach na dwa sposoby. Zwykle ogranicza to użycie do typów pierwotnych, wskaźników, niektórych form tablic, związków, struktur i tylko niektórych typów wskaźników do funkcji.

Ponieważ wiele innych języków programowania często udostępnia interfejs API C do pisania rozszerzeń lub uruchamiania interpretera języków, D może również łączyć się bezpośrednio z tymi językami, używając standardowych powiązań C (z plikiem interfejsu cienkiego D). Na przykład istnieją dwukierunkowe powiązania dla języków takich jak Python , Lua i innych języków, często wykorzystujących generowanie kodu w czasie kompilacji i metody odbicia typów w czasie kompilacji.

Interakcja z kodem C++

D przyjmuje liberalne, ale realistyczne podejście do współdziałania z kodem C++.

Dla kodu D oznaczonego jako extern(C++) określone są następujące funkcje:

  • Konwencje przerabiania nazw powinny odpowiadać konwencji C++ w miejscu docelowym.
  • W przypadku wywołań funkcji ABI jest równoważny.
  • Tablica vtable powinna być dopasowana do pojedynczego dziedziczenia (jedyny poziom obsługiwany przez specyfikację języka D).

Przestrzenie nazw C++ są używane poprzez składnię extern(C++, przestrzeń nazw), gdzie przestrzeń nazw jest nazwą przestrzeni nazw C++.

Przykład współdziałania w C++

Strona C++

#include <iostream>
using namespace std;
class Base
{
    public:
        virtual void print3i(int a, int b, int c) = 0;
};

class Derived : public Base
{
    public:
        int field;
        Derived(int field) : field(field) {}

        void print3i(int a, int b, int c)
        {
            cout << "a = " << a << endl;
            cout << "b = " << b << endl;
            cout << "c = " << c << endl;
        }

        int mul(int factor);
};

int Derived::mul(int factor)
{
    return field * factor;
}

Derived *createInstance(int i)
{
    return new Derived(i);
}

void deleteInstance(Derived *&d)
{
    delete d;
    d = 0;
}

Strona D

extern(C++)
{
    abstract class Base
    {
        void print3i(int a, int b, int c);
    }

    class Derived : Base
    {
        int field;
        @disable this();
        override void print3i(int a, int b, int c);
        final int mul(int factor);
    }

    Derived createInstance(int i);
    void deleteInstance(ref Derived d);
}

void main()
{
    import std.stdio;

    auto d1 = createInstance(5);
    writeln(d1.field);
    writeln(d1.mul(4));

    Base b1 = d1;
    b1.print3i(1, 2, 3);

    deleteInstance(d1);
    assert(d1 is null);

    auto d2 = createInstance(42);
    writeln(d2.field);

    deleteInstance(d2);
    assert(d2 is null);
}

Lepsze C

Język programowania D ma oficjalny podzbiór znany jako „ Lepsze C ”. Ten podzbiór zabrania dostępu do funkcji D wymagających użycia bibliotek wykonawczych innych niż C.

Włączone za pomocą flag kompilatora „-betterC” w DMD i LDC oraz „-fno-druntime” w GDC, Better C może odwoływać się tylko do kodu D skompilowanego z tą samą flagą (i połączonego kodu innego niż D), ale kodu skompilowanego bez Lepsza opcja C może wywołać kod skompilowany z nią: doprowadzi to jednak do nieco innych zachowań ze względu na różnice w sposobach obsługi asercji przez C i D.

Funkcje zawarte w Better C

  • Nieograniczone korzystanie z funkcji czasu kompilacji (na przykład funkcje dynamicznej alokacji D mogą być używane w czasie kompilacji do wstępnej alokacji danych D)
  • Pełne możliwości metaprogramowania
  • Zagnieżdżone funkcje, zagnieżdżone struktury, delegacje i lambdy
  • Funkcje składowe, konstruktory, destruktory, przeciążanie operacyjne itp.
  • Pełny system modułowy
  • Krojenie tablic i sprawdzanie granic tablic
  • RAII
  • zakres (wyjście)
  • Zabezpieczenia pamięci
  • Współpraca z C++
  • Klasy COM i klasy C++
  • błędy asercji są kierowane do biblioteki wykonawczej C
  • przełącznik ze sznurkami
  • ostatni przełącznik
  • bloki testów jednostkowych
  • walidacja formatu printf

Funkcje wyłączone z Better C

  • Zbieranie śmieci
  • TypeInfo i ModuleInfo
  • Wbudowane wątki (np. core.thread)
  • Tablice dynamiczne (chociaż działają plastry tablic statycznych) i tablice asocjacyjne
  • Wyjątki
  • zsynchronizowane icore.sync
  • Konstruktory lub destruktory modułów statycznych

Historia

Walter Bright rozpoczął pracę nad nowym językiem w 1999 roku. D został wydany po raz pierwszy w grudniu 2001 roku i osiągnął wersję 1.0 w styczniu 2007 roku. Pierwsza wersja języka (D1) koncentrowała się na imperatywnych, obiektowych i metaprogramowych paradygmatach, podobnych do C++.

Niektórzy członkowie społeczności D, niezadowoleni z Phobos, oficjalnego środowiska wykonawczego i standardowej biblioteki D , stworzyli alternatywne środowisko wykonawcze i standardową bibliotekę o nazwie Tango. Pierwsze publiczne ogłoszenie Tango pojawiło się kilka dni po wydaniu D 1.0. Tango przyjęło inny styl programowania, obejmujący OOP i wysoką modułowość. Będąc projektem prowadzonym przez społeczność, Tango było bardziej otwarte na wkłady, co pozwoliło mu rozwijać się szybciej niż oficjalna standardowa biblioteka. W tym czasie Tango i Phobos były niekompatybilne ze względu na różne interfejsy API obsługi środowiska uruchomieniowego (odśmiecacz pamięci, obsługa wątków itp.). Uniemożliwiło to wykorzystanie obu bibliotek w tym samym projekcie. Istnienie dwóch powszechnie używanych bibliotek doprowadziło do poważnego sporu ze względu na to, że niektóre pakiety używają Phobos, a inne Tango.

W czerwcu 2007 została wydana pierwsza wersja D2. Początek rozwoju D2 sygnalizował stabilizację D1. Pierwsza wersja języka została oddana do serwisu, otrzymując jedynie poprawki i poprawki wdrożeniowe. D2 wprowadził przełomowe zmiany w języku, zaczynając od pierwszego eksperymentalnego systemu const . D2 później dodał wiele innych funkcji językowych, takich jak domknięcia , czystość i obsługa paradygmatów programowania funkcjonalnego i współbieżnego. D2 rozwiązał również problemy z bibliotekami standardowymi, oddzielając środowisko wykonawcze od biblioteki standardowej. Zakończenie budowy portu D2 Tango ogłoszono w lutym 2012 roku.

Uwolnienie Andrei Alexandrescu książki „s D języka programowania w dniu 12 czerwca 2010 roku oznaczał stabilizację D2, która dziś jest powszechnie określany jako tylko«D».

W styczniu 2011 r. rozwój D przeniósł się z bazy bugtracker / patch-submission do GitHub . Doprowadziło to do znacznego wzrostu wkładu w kompilator, środowisko wykonawcze i bibliotekę standardową.

W grudniu 2011 roku Andrei Alexandrescu ogłosił, że D1, pierwsza wersja języka, zostanie wycofana 31 grudnia 2012 roku. Ostateczne wydanie D1, D v1.076, miało miejsce 31 grudnia 2012 roku.

Kod oficjalnego kompilatora D, kompilatora Digital Mars D autorstwa Waltera Brighta, został pierwotnie wydany na niestandardowej licencji , kwalifikującej się jako dostępne źródło, ale niezgodne z definicją open source . W 2014 roku front-end kompilatora został ponownie licencjonowany jako open source w ramach licencji Boost Software License . Ten ponownie licencjonowany kod wykluczył zaplecze, które zostało częściowo opracowane w firmie Symantec . 7 kwietnia 2017 r. cały kompilator został udostępniony na licencji Boost po tym, jak Symantec wyraził zgodę na relicencjonowanie również back-endu. W dniu 21 czerwca 2017 r. Język D został przyjęty do włączenia do GCC.

Realizacje

Większość obecnych implementacji języka D kompiluje się bezpośrednio do kodu maszynowego w celu wydajnego wykonania.

Kompilatory gotowe do produkcji:

  • DMDkompilator Digital Mars D autorstwa Waltera Brighta jest oficjalnym kompilatorem D; open source na licencji Boost Software License . Frontend DMD jest współdzielony przez GDC (obecnie w GCC) i LDC, aby poprawić kompatybilność między kompilatorami. Początkowo frontend został napisany w C++, ale teraz większość jest napisana w samym D (self-hosting). Optymalizatory zaplecza i kodu maszynowego są oparte na kompilatorze firmy Symantec. Początkowo obsługiwał tylko 32-bitowy x86, z dodaną obsługą 64-bitowego amd64 i PowerPC autorstwa Waltera Brighta. Później backend i prawie cały kompilator został przeniesiony z C++ do D w celu pełnego samodzielnego hostowania.
  • GCC – The GNU Compiler Collection , połączył GDC z GCC 9 w dniu 29 października 2018 r. Pierwsze działające wersje GDC z GCC, oparte na GCC 3.3 i GCC 3.4 na 32-bitowym x86 na Linuksie i macOS, zostały wydane 22 marca 2004 r. Od tego czasu potem GDC zyskiwało wsparcie dla większej liczby platform, poprawiało wydajność i naprawiało błędy, jednocześnie śledząc kod DMD dla frontendu i specyfikacji językowej.
  • LDC — kompilator oparty na interfejsie DMD, który wykorzystuje LLVM jako zaplecze kompilatora. Pierwsza wersja o jakości wydania została opublikowana 9 stycznia 2009 r. Obsługuje wersję 2.0.

Kompilatory zabawek i rozwiązań typu proof-of-concept:

  • D Compiler for .NET — zaplecze dla kompilatora języka programowania D 2.0. Kompiluje kod do kodu bajtowego Common Intermediate Language (CIL), a nie do kodu maszynowego. CIL można następnie uruchomić za pośrednictwem maszyny wirtualnej Common Language Infrastructure (CLI) . Projekt nie był aktualizowany od lat, a autor wskazał, że projekt nie jest już aktywny.
  • SDC — The Stupid D Compiler używa niestandardowego interfejsu i LLVM jako zaplecza kompilatora. Jest napisany w D i używa harmonogramu do obsługi rozwiązywania symboli w celu eleganckiej obsługi funkcji czasu kompilacji D. Ten kompilator obecnie obsługuje ograniczony podzbiór języka.

Korzystanie powyżej kompilatorów i toolchains, jest możliwe do kompilowania programów rozwojowych kierować wiele różnych architektur, w tym x86 , amd64 , AArch64 , PowerPC , MIPS64 , DEC Alpha , Motorola m68k , Sparc , S390 , WebAssembly . Podstawowymi obsługiwanymi systemami operacyjnymi są Windows i Linux , ale różne kompilatory obsługują również Mac OS X , FreeBSD , NetBSD , AIX , Solaris/OpenSolaris i Android , zarówno jako host lub cel, jak i oba. Cel WebAssembly (obsługiwany przez LDC i LLVM) może działać w dowolnym środowisku WebAssembly, takim jak nowoczesna przeglądarka internetowa ( Google Chrome , Mozilla Firefox , Microsoft Edge , Apple Safari ) lub dedykowane maszyny wirtualne Wasm.

Narzędzia programistyczne

Edytory i zintegrowane środowiska programistyczne (IDE) obsługujące podświetlanie składni i częściowe uzupełnianie kodu dla języka obejmują między innymi SlickEdit , Emacs , vim , SciTE , Smultron , Zeus i Geany .

  • Dexed (dawniej Coedit), graficzne IDE skoncentrowane na D napisane w Object Pascal
  • Mono-D to bogate w funkcje, wieloplatformowe, graficzne IDE oparte na MonoDevelop / Xamarin Studio, napisane głównie w C Sharp.
  • Wtyczki Eclipse dla D zawierają DDT i Descent (martwy projekt).
  • Integrację z programem Visual Studio zapewnia VisualD.
  • Integracja Visual Studio Code z rozszerzeniami jak Dlang-Vscode lub Code-D.
  • Dla TextMate dostępny jest pakiet , a środowisko Code::Blocks IDE zawiera częściową obsługę tego języka. Jednak standardowe funkcje IDE, takie jak uzupełnianie kodu lub refaktoryzacja, nie są jeszcze dostępne, chociaż działają częściowo w Code::Blocks (ze względu na podobieństwo D do C).
  • Xcode 3 wtyczki „D dla Xcode” umożliwia rozwój projektów i D-oparte.
  • Dostępna jest wtyczka autouzupełniania KDevelop (jak również jego backend edytora tekstu, Kate).

Istnieją otwarte IDE D dla systemu Windows , niektóre napisane w D, takie jak Poseidon, D-IDE i Entice Designer.

Aplikacje D można debugować za pomocą dowolnego debugera C/C++, takiego jak GDB lub WinDbg , chociaż obsługa różnych funkcji języka D jest bardzo ograniczona. W systemie Windows programy D można debugować za pomocą Ddbg lub narzędzi debugowania firmy Microsoft (WinDBG i Visual Studio), po przekonwertowaniu informacji debugowania za pomocą cv2pdb . ZeroBUGS debugger dla systemu Linux ma eksperymentalne wsparcie dla języka D. Ddbg może być używany z różnymi IDE lub z wiersza poleceń; ZeroBUGS posiada własny graficzny interfejs użytkownika (GUI).

DustMite to potężne narzędzie do minimalizowania kodu źródłowego D, przydatne podczas wyszukiwania problemów z kompilatorem lub testami.

dub to popularny menedżer pakietów i kompilacji dla aplikacji i bibliotek D, często zintegrowany z obsługą IDE.

Przykłady

Przykład 1

Ten przykładowy program wyświetla swoje argumenty wiersza poleceń. mainFunkcja jest punktem wyjścia programu D i argsjest tablicą ciągów reprezentujących argumenty wiersza poleceń. A stringw D to tablica znaków reprezentowanych przez immutable(char)[].

import std.stdio: writefln;

void main(string[] args) {
    foreach (i, arg; args)
        writefln("args[%d] = '%s'", i, arg);
}

foreachOświadczenie może iteracyjne nad każdą kolekcję. W tym przypadku tworzy sekwencję indeksów ( i) i wartości ( arg) z tablicy args. Indeks ii wartość argmają swoje typy wywnioskowane z typu tablicy args.

Przykład 2

Poniżej przedstawiono kilka możliwości D i kompromisów w projektowaniu D w krótkim programie. Iteruje po wierszach pliku tekstowego o nazwie words.txt, który zawiera inne słowo w każdym wierszu i drukuje wszystkie słowa, które są anagramami innych słów.

import std.stdio, std.algorithm, std.range, std.string;

void main() {
    dstring[] [dstring] signature2words;

    foreach (dchar[] w; lines(File("words.txt"))) {
        w = w.chomp().toLower();
        immutable signature = w.dup.sort().release().idup;
        signature2words[signature] ~= w.idup;
    }

    foreach (words; signature2words) {
        if (words.length > 1) {
            writeln(words.join(" "));
        }
    }
}
  1. signature2wordsjest wbudowaną tablicą asocjacyjną, która odwzorowuje klucze dstring (32-bitowe / char) na tablice dstringów. Jest podobny do defaultdict(list)w Pythonie .
  2. lines(File())daje wiersze leniwie, ze znakiem nowej linii. Należy go następnie skopiować za pomocą, idupaby uzyskać ciąg znaków, który będzie używany dla wartości tablicy asocjacyjnej ( idupwłaściwość tablic zwraca niezmienny duplikat tablicy, co jest wymagane, ponieważ w dstringrzeczywistości typem jest immutable(dchar)[]). Wbudowane tablice asocjacyjne wymagają niezmiennych kluczy.
  3. ~=Operatora dołącza nowy dstring do wartości stowarzyszonego macierzy dynamicznej.
  4. toLower, joinI chompsą funkcjami D ciąg znaków, który pozwala na stosowanie z składni metody. Nazwy takich funkcji są często podobne do metod napisowych Pythona. W toLowerprzekształca ciąg litery, join(" ")łączy matrycę łańcuchów w jeden ciąg, z zastosowaniem pojedynczego miejsca jako separatora i chompusuwa przełamane od końca łańcucha, jeśli jest obecny. w.dup.sort().release().idupJest bardziej czytelny, ale równoważne release(sort(w.dup)).idupnp. Ta funkcja nosi nazwę UFCS (Uniform Function Call Syntax) i umożliwia rozszerzenie dowolnych typów pakietów wbudowanych lub innych firm o funkcjonalność podobną do metody. Styl pisania takiego kodu jest często określany jako potok (zwłaszcza, gdy używane obiekty są obliczane leniwie, na przykład iteratory / zakresy) lub interfejs Fluent .
  5. sortJest funkcja std.algorithm że sortuje tablicę w miejscu, tworząc unikalny podpis dla słów, które są anagramy od siebie. release()Metoda na wartości zwracanej sort()jest poręczny zachować kod w postaci pojedynczego wyrazu.
  6. Drugi foreachiteruje na wartościach tablicy asocjacyjnej, jest w stanie wywnioskować typ words.
  7. signature jest przypisywana do niezmiennej zmiennej, jej typ jest wywnioskowany.
  8. UTF-32 dchar[] jest używany zamiast normalnego UTF-8, w char[] przeciwnym razie sort()odmawia jego sortowania. Istnieją bardziej wydajne sposoby na napisanie tego programu przy użyciu tylko UTF-8.

Zastosowania

Znane organizacje, które używają języka programowania D w projektach, to Facebook , eBay i Netflix .

D jest z powodzeniem stosowany w grach AAA , interpreterach języków, maszynach wirtualnych, jądrze systemu operacyjnego , programowaniu GPU , tworzeniu stron internetowych , analizie numerycznej , aplikacjach GUI , systemie informacji pasażerskiej , uczeniu maszynowym, przetwarzaniu tekstu, serwerach WWW i aplikacji oraz badaniach.

Zobacz też

Bibliografia

Dalsza lektura

Zewnętrzne linki