D/IntroC
Wprowadzanie dla programistów C/C++
[edytuj]Jeśli jesteś doświadczonym programistą, albo po prostu czujesz się swobodnie w językach C lub C++, ten rozdział jest dla Ciebie. Może być on również przydatny, dla osób, znających inne języki, a którzy nie chcą marnować czasu na wstęp do programowania. Łatwo się odnajdą tutaj osoby znające składnie języków podobnych do C, np. Java, C#. Również osoby programujące w takich językach jak Python, Pascal, PHP, nie powinny mieć większych problemów, ponieważ podstawowe elementy (zmienne, typy, kompilacja/uruchomienie, instrukcje warunkowe, pętle, funkcje) są bardzo podobne.
Do osób sceptycznie nastawionych do różnych pomysłów, szczególnie w kwestii wydajność różnych aspektów języka, apelujemy o powstrzymanie się tymczasowo od krytyki, ponieważ, większość decyzji projektowych języka ma swoje uzasadnienie, a fakt, iż język D jest jednym z najszybszych (drugi w popularnym teście Shootout Debiana) języków na świecie (wbrew intuicji) świadczy jedynie o niepełnym zrozumieniu pewnych aspektów (np. automatycznego odśmiecania pamięci, funkcji wirtualnych, optymalizacji kompilatora, obsługi tablic, stosu, wyjątków itp, itd).
Start
[edytuj]Najprostszym możliwym programem na rozpoczęcie jest oczywiście Hello World.
import std.stdio; void main() { writefln("Hello World"); }
Aby skompilować program kompilatorem dmd:
$ dmd hello.d
I uruchamiamy:
$ ./hello Hello World $
Można też wykonać kompilacje i uruchomienie w jednej komendzie:
$ dmd -run ./hello.d Hello World $
Przydaje się to przy pisaniu skryptów, wtedy w pierwszej linii skryptu możemy dodać:
#!/usr/bin/dmd -run
aby po prostu stosować normalne uruchomienie (pamiętaj o dodaniu atrybuty wykonywalności: chmod +x ./hello.d):
$ ./hello.d
Pełna sygnatura funkcji main to: int main(char[][] args).
Proste IO
[edytuj]Funkcja writef/writefln znajdująca się w module std.stdio, jest bardzo podobna do printf z C, może przyjmować wiele argumentów, jest jednak trochę wygodniejsza i bezpieczniejsza jeśli chodzi o typy (funkcja printf jest dostępna w module std.c.stdio, jednak nie radzi się jej używać o czym za chwilę). Trochę przykładów
writef("Hello %s\n", "World"); writefln("Mam %d lat i %f m wzorstu", 21, 1.76); int a = 5, b = 66; writefln("a=", a, " b=", b, " a+b=", a+b);
Oba sposoby można mieszać. writefln w trakcie wykonania sprawdza typy argumentów, i podejmuje różne działania, jeśli typy są sprzeczne, to funkcja rzuca wyjątek czasu wykonania.
writefln("%d", 1.2); // rzuci wyjątek: Error: std.format floating
Format %s oprócz wypisywania łańcuchów służy za meta format, można go zastosować do dowolnego typu, i zostanie użyty domyślny format dla tego typu, a w przypadku obiektów, zostanie użyta metoda char[] toString(). W przypadku tablic zostaną wypisane wszystkie składniki, oddzielone przecinkami (z [ i ] odpowiednio na początku i końcu), a obiekty złożone zostaną obsłużone rekurencyjnie.
Podstawowa arytmetyka
[edytuj]Podstawowa arytmetyka jest zgodna ze standardową notacją C i innych języków:
int a, b, c, d, e; a = 11; b = a*2; c = b - a / 3; b -= c+a;
Priorytety operatorów są praktycznie identyczne jak w C, zawsze można wymusić priorytety nawiasami. Aktualnie nie ma operatora potęgowania.
Zmienne
[edytuj]Zmienne w D, można definiować w praktycznie dowolnym miejscu programu, a nie jak w C, tylko na początku bloku. Zmienne są automatycznie inicjalizowane (dla zmiennych całkowitych jest to 0, dla zmiennoprzecinkowych jest to wartość NaN, a dla bool: false, dla char: 0xFF).
import std.stdio; int main() { writefln("Trochę zmiennych:"); int a = 6; int b; double c, d; d = 55.1 writefln(a, " ", b, " ", c, " ", d); return 0; }
Da:
Trochę zmiennych: 6 0 nan 55.1
Również w przypadku tablic, wszystkie elementy tablicy są inicjalizowane. Można nie inicjalizować zmiennych przy pomocy przypisania im void przy inicjalizacji.
void f() { int duza[1000] = void; }
Należy używać tego tylko w bardzo wyjątkowych sytuacjach (prowadzi to do błędów, nawet jeśli jest bezpiecznie używane to może mylić garbage collector [choć bieżąca implementacja już powinna być na to odporna], i prowadzić do innych przykrych błędów, nawet jeśli jest prawidłowo używana). Używać tylko przy krytycznie ważnych funkcjach i jeśli tablica/zmienna jest zaraz wypełniania wartościami.
Typy
[edytuj]Podstawowymi typami zmiennych są: liczby ze znakiem: int (32 bity), short (16), byte (8), long (64), cent (128, zarezerwowane); typy bez znaku uint, ushort, ubyte, ulong, ucent; zmiennoprzecinkowe: float (pojedyncza precyzja), double (podwójna precyzja), real (największa precyzja, 80(128) bitów na i387); typy urojone i zespolone: ifloat, idouble, ireal, cfloat, cdouble, creal. Typ logiczny: bool (zajmuje 8 bitów). Oraz typy znakowe: char (8 bitów), dchar (16), wchar (32).
Właściwości
[edytuj]Typy mają pewne właściwości (properties) (jak również zmienne tych typów, ale są one naprawdę atrybutami typów). Są one tylko do odczytu. Mianowicie .init - inicjalizator typu. .sizeof - wielkość zmiennej w bajtach, .alignof - rozmiar wyrównania, .mangleof - string reprezentujący tzw. mangled (wewnętrzną) reprezentacje typu (używaną przez kompilator, linker i specjalne narzędzia, np. do refleksji), .stringof - reprezentacja łańcuchowa źródła typu lub wartości.
Dodatkowo:
Dla typów całkowitych: .init - 0, .min, .max - wartości minimalne i maksymalne.
Dla typów zmiennoprzecinkowych: .init - NaN, .infinity - nieskończoność, .nan - wartość NaN, .dig - ilość cyfr dziesiętnych precyzji, .epsilon - najmniejszy przyrost do liczby 1, .mant_dig - ilość bitów w mantysie, .max_10_exp - maksymalny int taki że 10^10max_10_exp jest reprezentowalny, .max_exp - maksymalny int taki że 2^max_exp-1 jest reprezentowalny, .min_10_exp - minimalny int taki że 10^min_10_exp jest reprezentowalny jako liczba znormalizowana, .min_exp - minimalny int taki że 2^min_exp-1 jest reprezentowalny jako liczba znormalizowana, .max - największa liczba reprezentowalna różna od nieskończoności, .min najmniejsza znormalizowana wartość różna od 0, .re - część rzeczywista, .im - część urojona.
Literały
[edytuj]Literały odpowiadające tym typom są podobne do tych w innych językach, jednak umożliwiono na wiele udogodnień:
int a = 123; int duza = 123_444_555; // można rozdzielać cyfry podkreśleniem int b = 0xb1; // szesnastkowo 177 int c = 0660; // ósemkowo int d = 0b0101001010; // dwójkowo (binarnie) float x = 1.FFA2g11; // liczba zmiennoprzecinkowa zapisana szesnastkowo, // po g jest wykładnik przy potędze 2
Literały typu bool (logiczne) to: true i false.
Literały urojone kończą się literą i:
ifloat i = 1.0i;
Literały zespolone są tworzone w fazie semantycznej z sąsiadujących literałów rzeczywistego i urojonego połączonych znakiem plus lub minus:
cdouble phi = 0.55 + 0.15i;
Logika
[edytuj]Zmienne logiczne (typu bool) mają 1 bajt i mogą przechowywać tylko true albo false. Jedynymi dozwolonymi operatorami są: & | ^ &= |= ^= ! && || ?:. Wartość typu bool mogą być automatycznie skonwertowane na typ całkowity (false -> 0, true -> 1). Podobnie w drugą stronę: (0 -> false, 1 -> true, inne -> błąd!). Jawne rzutowanie wyrażenia oznacza porównywanie z 0 (tzn == 0, albo != 0) dla typów arytmetycznych, albo null lub != null dla wskaźników i referencji.
Typy złożone
[edytuj]Typami złożonymi są: wskaźniki, tablice, tablice asocjacyjne, funkcje oraz delegaty.
Tablice
[edytuj]Tablice (zbiór zmiennych tego samego typu, indeksowany liczbami) tworzy się przy pomocy składni podobnej do tej z C.
int t[100]; // tworzy tablicę 100 liczb typu int i je wypełnia 0
Dozwolona (a wręcza zalecana) jest odwrotna składnia:
int[100] t;
Przy tworzeniu kilku zmiennych wszystkie muszą być tego samego typu:
int a[100], b[100];
Co jest bardziej oczywiste przy zapisie lewostronnym:
int[100] a, b; // a i b są tablicami 100 intów
Błędem jest:
int a[100], b[101]; // BŁĄD: różne typy!
Tablice obsługuje się standardowo:
int x = a[37]; a[37] = 4;
W wypadku tablic wielowymiarowych:
int c[10][100]; // 10 tablic każda o 100 elementach int
albo:
int[100][10] c; // UWAGA: Odwrotna kolejność
ewentualnie:
int[100] c[10];
Dostęp do takiej tablicy jest standardowy dla C (indeksuje się od 0):
int l = c[56][5];
Ponieważ rozmiary tablic są znane w czasie kompilacji, tak naprawdę zmienna tablicowa to nie tylko wskaźnik, ale para wskaźnik oraz rozmiar. Jest to bardzo pomocne (w C i tak zwykle tworzy się dodatkową zmienną na pamiętanie rozmiaru) przy przekazywaniu jej do funkcji. Dzięki temu również jest sprawdzana poprawność indeksów:
int l2 = c[56][10]; // BŁĄD: tablica c[56] ma tylko elementy 0..9
Tego typu błąd powinien zostać zauważony już na etapie kompilacji, a na pewno w czasie wykonania programu (chyba, że skompilowany z opcją -release, która wyłącza takie sprawdzania, ponieważ spowalniają one oczywiście program).
Tablice mają dzięki temu dodatkową właściwość .length, zawierającą rozmiar tablicy (ilość elementów). .length można zmieniać, jednak należy się liczyć z konsekwencjami. Tablice mają również właściwość .ptr, która jest wskaźnikiem jak w C, na początek tablicy. (kiedyś przy rzutowaniu na typ wskaźnikowy, był pobierany właśnie .ptr).
Literały tablicowe
[edytuj]Tablice można inicjalizować przy pomocy następującej składni:
int[5] a = [1,5,1,77,66];
Można pominąć rozmiar, wtedy zostanie on dobrany automatycznie:
int[] a = [1,5,1,77,66]; // a jest zmienną int[5]
Tak więc a nie jest tzw. tablicą dynamiczną (o czym za chwilę). Typy elementów muszą się zgadzać, w wypadku niejednoznaczności używany jest typ pierwszego elementu, a reszta jest rzutowana (o ile automatyczne rzutowanie jest dozwolone).
Stringi
[edytuj]Podobnie:
char[10] a;
To para wskaźnik oraz długość. Dzięki temu nie jest potrzebne dodatkowe sprawdzanie długości stringów, i co ważne stringi takie nie mają wbudowanego na końcu znaku '\0'. Dlatego używanie funkcji printf jest niebezpieczne (która poza tym i tak musi przyjąć a.ptr, a nie a).
char[] a; // .. stwórz a printf("%s\n", a.ptr); // wykrzaczy się, ponieważ a nie ma 0 na końcu
Literały stringowe mają jednak go wbudowanego (a "literal".lenght jest oszukany i jest o jeden mniejszy, choć w pamięci zabiera o jeden bajt więcej. Pozostawiono to celem współpracy w C. Literały takie są automatycznie rzutowane na char*). Dlatego to jest poprawne:
printf("Test\n"); // ponieważ literał ten zawiera 0 na końcu.
Stringi w D są więc zwykłymi tablicami. Stringi używają kodowania UTF8. W wypadku zwykłych znaków ASCII nie zmienia to niczego, jednak umożliwia łatwiejsze używanie znaków międzynarodowych: znaki wtedy są te kodowane przy pomocy dwóch bajtów (na szczęście są one używane w miarę rzadko i nie powoduje to dużego wzrostu zużycia pamięci). Wiążą się z tym problemy: .length niekoniecznie jest teraz ilością liter w stringu (poza tym Unicode obsługuje specjalne meta znaki i akcent, co jeszcze bardziej komplikuje sprawę). Warto jedynie wiedzieć, że większość funkcji z std.string radzi sobie z UTF-8 (ponieważ UTF-8 został zaprojektowany, aby w większości kwestii nie trzeba było modyfikować algorytmów). A jeśli chcemy możemy rzutować taki string na tablicę wchar[], wtedy można indeksować znak po znaku (albo iterować pętlą foreach).
W literałach tekstowych można używać znaków Unicode (ponieważ kod źródłowy jest zapisany zwykle w UTF-8, domyślnie - inne zachowanie można przełączyć tzw. BOM).
writefln("Zażółć gęślą jaźń");
Również dowolne identyfikatory mogą mieć znaki Unicode:
float prędkość = 55.6;
Nie powinno się używać zmiennych rozpoczynających się od znaku podkreślenia.
Np. prezentowana dalej pętla foreach:
char[] tekst = "Zażółć gęslą jaźń" foreach (char znak; tekst) { writefln(znak); }
nie wyświetli znak po znaku tego tekstu, ponieważ znak nie potrafi przechowywać np. 'ż', i na ekranie będzie trochę śmieci. Prawidłowo:
char[] tekst = "Zażółć gęslą jaźń" foreach (dchar znak; tekst) { // dchar ma 16 bitów i polskie znaki obsłuży writefln(znak); }
Dynamiczne tablice
[edytuj]Tablice dynamiczne to tablice których rozmiar możemy zmieniać.
int[] a; // tworzy tablicę o długości 0 (nie podaje się rozmiaru) a.length = 5; // zmienia rozmiar na 5, inicjalizuje nowe miejsca zerami a[3] = 11; a.length = 8; // realokuje a[6] = 33; a.length = 5; // realokuje (być może niszczy obiekty i przenosi w inne miejsce) a[6] = 44; // błąd
Ważne jest iż:
int[] a = [1,5,7,8]; // to też tworzy tablicę dynamiczną
to to samo co:
int[] a; a = [1,5,7,8];
natomiast
int[4] b = [1,5,7,8];
tworzy tablicę statyczną (chociaż inicjalizacja wartościami zostanie przeprowadzona dopiero przy wykonaniu tej linijki). Np. w drugim wypadku możemy zniszczyć tablicę prosto:
a = [66,88]; // zmieniamy na inną
albo dodać (a dokładniej dopisać) prosto element:
a ~= 101; // teraz mamy [66,88,101];
Tablice takie łatwo możemy inicjalizować też:
a[] = 5; // cała tablica piątek a = b; // kopiuje tablicę
Wycinanie
[edytuj]Ponieważ tablice są parą wskaźnik oraz długość, można łatwo wycinać tablice, zmieniając końce (a więc te pary).
int[] a = [1,2,3,4,5]; // zwykła tablica int[] b = a[1..3]; // b będzie [2,3]
Drugi argument wskazuje za ostatni element. Zmienna b wskazuje na te same elementy! A więc zmiana w jednej z nich zmienia drugie. Można używać specjalnej zmiennej length (lub jej synonimu $) wewnątrz nawiasów. Jeśli zależy nam na innej zmiennej length, należy dodać kropkę.
int[] a; a = [1,2,5,7,8,1,23,4]; int[] b = a[4..$]; // [8,1,23,4] int length = 5; int[] b2 = a[4..length]; // to samo co b int[] b3 = a[4 .. .length]; // [8]
Możemy również w ten sposób konwertować wskaźniki (np. z C) na tablice dynamiczne (ze sprawdzaniem indeksów):
int *p = a.ptr; int[] c = p[0..8];
char *temp = funkcje_z_c(); char[] s = temp[0 .. strlen(temp)];
Albo przypisywać pewnemu zakresowi tablicy wartości grupowo:
a[1..3] = 100; // da [1,100,100,100,8,1,23,4]
Tablice haszujące
[edytuj]Są to tablice dynamiczne indeksowane innymi typami (np. stringami).
float[char[]] ceny; ceny["czapka"] = 15.5; ceny["mleko"] = 2.1;
Aktualna implementacja tablic asocjacyjnych znajduje się w pliku Aa.d w bibliotece standardowej. Aktualnie tablice te nie zachowują żadnego porządku, można indeksować dowolnymi obiektami (można udostępnić metodę uint toHash() oraz int opCmp(KeyType* s)).
Literały dla tablic haszujących mają następującą składnię (dostępne od DMD 1.014):
float[char[]] ceny; ceny = ["mleko": 2.1, "chleb": 1.9];
Typy klucza i wartości są wnioskowane na podstawie pierwszego elementu, a reszta jest rzutowana niejawnie na te typy (tutaj char[5], float). W przypadku łańcuchów znaków więc trzeba dodatkowo zrobić rzutowanie:
ceny = [cast(char[])"mleko": 2.1, "chleb": 1.9, "sok": 3.4]; // bo "sok" jest char[3] i nie można rzutować do char[5] ("mleko")
Własne tablice
[edytuj]Operatory [] używane do przypisywania, wczytywania, oraz wycinania można zaimplementować we własnej klasie (np. w celu stworzenia "kontentera", zbiorów różnego typu, czy innych typów dynamicznych, np. drzew, list, itp).
Iterowanie
[edytuj]Poza pętlą for, while, do..while taką jak w C:
for (int i = 0; i < 10; i++) { writefln("Haha"); }
Dostępna jest również pętla foreach, która ułatwia iterowanie po kolekcjach:
int[] a = [1,5,6]; foreach (int i, int v; a) { // i to indeks, v to wartość writefln(i, " element tablicy a to ", v); }
Co można skrócić:
foreach (i, v; a) { // typy i oraz v zostaną automatycznie dobrane writefln(i, " element tablicy a to ", v); }
Albo jeśli nie potrzebujemy indeksu:
foreach (v; a) { // typy i oraz v zostaną automatycznie dobrane writefln("w a jest ", v); }
Jeśli chcemy modyfikować elementy podczas iteracji należy użyć atrybutu inout (indeksów nie wolno modyfikować, jak również nie powinno się modyfikować długości tablicy, ani np. dodawać elementów do kontenera):
foreach (i, inout v; a) { writefln("w a było a[%d]", i, v); v = i; }
Istnieje również foreach_revese który iteruje w przeciwnym kierunku. foreach można zaimplementować w własnej klasie przy pomocy opApply (która przyjmuje delegata o różnej ilości argumentów). Bardzo to ułatwia pisanie różnego kodu.
Kompilator sam się zatroszczy o wybranie optymalnego sposobu iteracji (liczbami, a może wskaźnikami, itp).
W przypadku tablic haszujących foreachem można iterować po parach klucz, wartość (porządek iteracji nie jest zachowany!, przynajmniej w bieżącej implementacji):
char[][char[]] telefony; telefony["Witek"] = "123-123"; telefony["Jacek"] = "165-163"; telefony["Marcin"] = "667-121"; foreach(kto, t; telefon) { // wnioskowanie typów writefln("Osoba ", kto, " ma numer ", t); }
Typy definiowane
[edytuj]Służą do tego słowa kluczowe: alias, typedef, enum, struct, union oraz class. W celu dowiedzenia się o nich więcej zajrzyj do rozdziału Typy złożone. Najważniejsze fakty to to, że enum używa się jak w C.
enum Karty { PIK, TREFL, KARO, KIER // duże litery to tylko konwencja }
alias tworzy inną nazwę typu (np. krótszą):
alias char[][char[]] mapa; // tablica haszujaca stringów w stringi
typdef z kolei tworzy nowy typ:
typedef int cena = -1;
Możemy redefiniować nowy .init dla takiego typu. Przypisywanie pomiędzy typem int i cena jest niedozwolone bez jawnego rzutowania:
int a = 6; cena b = a; // BŁĄD cena c; // -1
Przeładowanie funkcji działa dla nich oddzielnie:
f(int a) ... f(cena a)...
to dwie różne funkcje.
Atrybuty, wnioskowanie typu
[edytuj]Atrybuty dostępu: public, private, protected.
Atrybuty inne: volatile (ulotne, ważne przy wielowątkowych aplikacjach), const (nie wolno zmieniać), static (składniki statyczne).
Automatyczne wnioskowanie typu:
char[][][char[]] x; x["as"] = ["vv", "gv", "nn"];
auto copy = x; // kopia będzie takiego samego typu co x
Automatyczne wnioskowanie jest również używane bez auto, jeśli pominiemy typ przy innym atrybucie (np. const, static, scope):
const x = 5; // to samo, co const int x static t = [4,5,6]; // to samo, co static int t[3]
Ma ono również zastosowanie w foreach (częściowo) oraz w szablonach.
Atrybut zasięgu:
{ scope Plik t = new Plik("dane.txt"); // ... } // po wyjściu t zostanie zniszczony
możemy pominąć nazwę typu: wnioskowanie
{ scope t = new Plik("dane.txt"); ... }
Atrybut zasięgu wyrażenia:
{ stworzy_plik_tymczasowy(); scope(failure) writefln("Wystapil nieprzewidziany blad, usuwam pliki"); scope(exit) usuń_plik_tymczasowy(); ... ... zróbmy_błąd(); // np. wyjątek, albo break, albo goto, albo return, coś co spowoduje opuszczenie zasięgu ... ... } // to co przy scope zostanie wykonane, jeśli nastąpi wyjście lub błąd
(oraz scope(failure), scope(success), do wyjątków), ułatwiają cofanie różnych transakcji oraz umieszczają kod obsługi błędu w jednym miejscu.
Funkcje
[edytuj]Funkcje się jedynie definiuje, nie są potrzebne deklaracje, nie ma potrzeby używania plików nagłówkowych, funkcje i zmienne globalne, można używać przed ich deklaracją. Przykłady:
import std.stdio; void main() { writefln(cos()); } int cos() { return 42; }
Zmienne wbudowane, enumy oraz struktury są przekazywane przez wartość. Tablice oraz obiekty klas są przekazywane przez referencję. W przypadku tablic domyślna jest semantyka COW (kopiowanie przy zapisie) mówiąca, że nie jest robiona kopia dopóki nie jest to ostatecznie wymagane (przynajmniej tak robią funkcje biblioteki standardowej ze względu na wydajność). Kopię tablicy można zrobić poprzez "metodę" .dup zmiennej tablicowej.
Parametry inout
[edytuj]W celu eliminacji użycia wskaźników do modyfikowania zmiennych spoza funkcji używa się parametrów inout (oraz out, in, ten ostatni jest domyślny):
import std.stdio; void main() { int a = 55; cos(a); writefln("a=%d", a); } void cos(inout int x) { x += 10; }
Wyświetli 65. W nowej wersji istnieje tez synonim inout: ref.
Zagnieżdżanie
[edytuj]Funkcje można zagnieżdżać w funkcjach (funkcje zagnieżdżone mają dostęp do zmiennych i parametrów formalnych funkcji na wyższym poziomie).
import std.stdio; void main() { writefln(cos(55, 66)); } int cos(int x, int y) { int roznicarazy(int r) { return r*(x-y); } // możemy używać x, y return roznicarazy(5) + x + y; }
Funkcje z void
[edytuj]W funkcjach void wychodzi się przez return bez argumentów (lub z, wtedy to co jest przy return jest obliczane, ale nie jest zwracane).
Funkcję z C
[edytuj]Deklaracje funkcji z C, Pascal, Fortrana czy Win32 obejmuje się atrybutem extern(C) itp.
Jeśli chce się aby dana funkcja zdefiniowana w D, mogła być wywoływana z C należy dodać jej taki atrybut (zmienia się wtedy między innymi konwencja przekazywania parametrów, zachowanie funkcji zmiennoargumentowych).
"Inlinowanie"
[edytuj]Kompilator sam decyduje kiedy "inlainować" funkcję lub metodę.
Funkcje czasu kompilacji
[edytuj]Jeśli funkcja jest używana w kontekście inicjalizacji zmiennnej statycznej lub const, albo przekazywana jako wyrażenie do szablonu. Funkcja jest wykonywana w czasie kompilacji.
int sqr(int a) { return a*a; } int main() { static x = sqr(5); // wykona się w trakcie kompilacji int x = sqr(5); // w trakcie wykonania writefln(sqr(5)); // w trakcie wykonania writefln(eval!(sqr(5))); // w trakcie kompilacji const y = 6; const x2 = sqr(y); // w trakcie kompilacji int y2 = 6; const x3 = sqr(y2); // w trakcie wykonania } template eval(E) { alias E eval; }
Funkcje czasu kompilacji mogą używać jedynie stałych parametrów (literałów albo innych stałych znanych w czasie kompilacji zmiennych stałych czy statycznych), mogą używać tablic, literałów, struktur, pętli, oraz innych funkcji nienaruszających tych zasad. Nie wolno używać klas, plików, instrukcji IO (poza pragma(msg, "Komunikat")).
Pozwala to na przykład na prekalkulowanie pewnych stałych czy ich stablicowanie już na etapie kompilacji, albo na łatwe tworzenie kodu do wykorzystania przez mixiny.