D Programlama Dili – Programlama dersleri ve D referansı
Ali Çehreli

çıkarsama: [deduction, inference], derleyicinin kendiliğinden anlaması
çöp toplayıcı: [garbage collector], işi biten nesneleri sonlandıran düzenek
değişmez: [immutable], programın çalışması süresince kesinlikle değişmeyen
evrensel: [global], modül düzeyinde tanımlanmış
fonksiyonel programlama: [functional programming], yan etki üretmeme ilkesine dayalı programlama yöntemi
hata ayıklama: [debug], programın hatalarını bulma ve giderme
iş parçacığı: [thread], işletim sisteminin program işletme birimi
referans: [reference], asıl nesneye, onun takma ismi gibi erişim sağlayan program yapısı
sabit: [const], bir bağlamda değiştirilmeyen
statik: [static], derleme zamanında belirli olan
tanımsız davranış: [undefined behavior], programın ne yapacağının dil tarafından tanımlanmamış olması
yükleme: [overloading], aynı isimde birden çok işlev tanımlama
... bütün sözlük



İngilizce Kaynaklar


Diğer




Diğer İşlev Olanakları

İşlevleri daha önce aşağıdaki bölümlerde görmüştük:

Burada o bölümlerde yer almayan başka işlev olanaklarını göreceğiz.

Dönüş türü olanakları

İşlevler auto, ref, inout, ve auto ref olarak bildirilebilirler. Bunlar işlevlerin dönüş türleriyle ilgilidir.

auto işlevler

auto olarak bildirilen işlevlerin dönüş türlerinin açıkça yazılması gerekmez:

auto topla(int birinci, double ikinci) {
    double sonuç = birinci + ikinci;
    return sonuç;
}

Derleyici dönüş türünü return satırından otomatik olarak çıkarsar. Yukarıdaki işlevin return ile döndürdüğü sonuç double olduğundan, o işlev sanki dönüş türü açıkça double yazılmış gibi derlenir.

Birden fazla return deyimi bulunduğunda işlevin dönüş türü o dönüş ifadelerinin ortak türüdür. (Ortak türü Üçlü İşleç ?: bölümünde görmüştük.) Örneğin, int ve double türlerinin ortak türü double olduğundan aşağıdaki auto işlevin dönüş türü double'dır:

auto işlev(int i) {
    if (i < 0) {
        return i;      // Burada 'int' döndürülüyor
    }

    return i * 1.5;    // Burada 'double' döndürülüyor
}

void main() {
    // İşlevin dönüş türü, 'int' ve 'double' türlerinin ortak
    // türü olan 'double' türüdür
    auto sonuç = işlev(42);
    static assert(is (typeof(sonuç) == double));
}
ref işlevler

İşlevlerin döndürdükleri değerler normalde işlevi çağıran tarafa kopyalanırlar. ref belirteci, dönüş değerinin kopyalanmak yerine referans olarak döndürülmesini sağlar.

Örneğin, aşağıdaki işlev kendisine verilen iki parametreden büyük olanını döndürmektedir:

int büyüğü(int birinci, int ikinci) {
    return (birinci > ikinci) ? birinci : ikinci;
}

O işlevin hem parametreleri hem de dönüş değeri normalde kopyalanır:

import std.stdio;

void main() {
    int a = 1;
    int b = 2;
    int sonuç = büyüğü(a, b);
    sonuç += 10;                  // ← ne a ne de b etkilenir
    writefln("a: %s, b: %s, sonuç: %s", a, b, sonuç);
}

büyüğü işlevinin dönüş değeri sonuç değişkenine kopyalandığından, değişkenin arttırılması yalnızca sonuç isimli kopyayı etkiler. İşleve kendileri de zaten kopyalanarak geçirilmiş olan a ve b değişmezler:

a: 1, b: 2, sonuç: 12

Parametrelerin kopyalanmak yerine referans olarak gönderilmeleri için ref anahtar sözcüğünün kullanıldığını biliyorsunuz. Aynı sözcük dönüş türü için de kullanılabilir ve işlevin dönüş değerinin de referans olarak döndürülmesini sağlar:

ref int büyüğü(ref int birinci, ref int ikinci) {
    return (birinci > ikinci) ? birinci : ikinci;
}

Bu durumda döndürülen referans parametrelerden birisinin takma ismi yerine geçecek ve onda yapılan değişiklik artık ya a'yı ya da b'yi değiştirecektir:

    int a = 1;
    int b = 2;
    büyüğü(a, b) += 10;           // ← ya a ya b etkilenir
    writefln("a: %s, b: %s", a, b);

Dikkat ederseniz, işlevin döndürdüğü referansı sonuç diye bir değişken kullanmadan doğrudan arttırıyoruz. O işlem, a ve b'den büyük olanını etkiler:

a: 1, b: 12

Yerel referans için gösterge gerekir: Burada bir noktaya dikkatinizi çekmek istiyorum. ref kullanılmış olmasına rağmen, o dönüş değerinin yerel bir değişkene atanması a veya b'yi yine de değiştirmez:

    int sonuç = büyüğü(a, b);
    sonuç += 10;               // ← yine yalnızca 'sonuç' değişir

büyüğü işlevi a'ya veya b'ye referans döndürüyor olsa da, o referans sonuç ismindeki yerel değişkene kopyalandığından a veya b değişmez:

a: 1, b: 2, sonuç: 12

sonuç'un a'nın veya b'nin referansı olması istendiğinde gösterge olarak tanımlanması gerekir:

    int * sonuç = &büyüğü(a, b);
    *sonuç += 10;
    writefln("a: %s, b: %s, sonuç: %s", a, b, *sonuç);

sonuç artık ya a'ya ya da b'ye erişim sağladığından, onun aracılığıyla yapılan değişiklik o ikisinin büyük olanını etkilerdi:

a: 1, b: 12, sonuç: 12

Yerel değişkene referans döndürülemez: Yukarıdaki ref dönüş değeri daha işlev çağrılmadan önce yaşamaya başlayan iki değişkenden birisinin takma ismi gibi çalışmaktadır. Bir başka deyişle, birinci de döndürülse ikinci de döndürülse o dönüş değeri hep işlevin çağrıldığı noktada zaten yaşamakta olan a'nın veya b'nin referansıdır.

Yaşam süresi işlevden çıkılırken sona erecek olan bir değişkene referans döndürülemez:

ref string parantezİçinde(string söz) {
    string sonuç = '(' ~ söz ~ ')';
    return sonuç;    // ← derleme HATASI
} // ← sonuç'un yaşamı burada sona erer

İşlev kapsamında tanımlanmış olan sonuç'un yaşamı o işlevden çıkıldığında sona erer. O yüzden, o değişkenin takma ismi gibi kullanılacak bir dönüş değeri olamaz.

Derleme, yerel değişkene referans döndürüldüğünü bildiren bir hata ile sonlanır:

Error: escaping reference to local variable sonuç
auto ref işlevler

Yukarıdaki parantezİçinde işlevinin yaşam süresi sona eren yerel değişken nedeniyle derlenemediğini gördük. auto ref öyle durumlarda yararlıdır.

auto ref olarak bildirilmiş olan bir işlevin dönüş türü auto işlevlerde olduğu gibi otomatik olarak çıkarsanır. Ek olarak, referans olamayacak bir değer döndürüldüğünde o değer referans olarak değil, kopyalanarak döndürülür.

Aynı işlevi auto ref olarak yazdığımızda program derlenir:

auto ref parantezİçinde(string söz) {
    string sonuç = '(' ~ söz ~ ')';
    return sonuç;                   // ← derlenir
}

İşlevin değer mi yoksa referans mı döndüreceği içindeki ilk return deyimi tarafından belirlenir.

auto ref daha çok parametrelerin duruma göre referans veya kopya olabildikleri işlev şablonlarında yararlıdır.

inout işlevler

Bu belirteç işlev parametrelerinde ve dönüş türünde kullanılır ve o işlevin parametrelerine bağlı olarak const, immutable, veya değişebilen anlamına gelir.

Yukarıdaki işlevi string alacak ve string döndürecek şekilde tekrar yazalım:

string parantezİçinde(string söz) {
    return '(' ~ söz ~ ')';
}

O işleve string türünde parametre verilmesi gerektiğini ve sonucunun string olduğunu biliyoruz:

    writeln(parantezİçinde("merhaba"));

"merhaba" hazır değerinin türü string, yani immutable(char)[] olduğundan o kod derlenir ve çalışır:

(merhaba)

Burada kullanışsız bir durum vardır: O işlev string türüne bağlı olarak yazıldığından immutable olmayan bir dizgi ile çağrılamaz:

    char[] dizgi;         // elemanları değişebilir
    dizgi ~= "selam";
    writeln(parantezİçinde(dizgi));    // ← derleme HATASI

Derleme hatası, değişebilen karakterlerden oluşan char[] türünün string'e dönüştürülemeyeceğini bildirir:

Error: function deneme.parantezİçinde (string söz)
is not callable using argument types (char[])

Aynı sorun const(char)[] dizgilerinde de vardır.

Bu sorunu çözmek için bir kaç yöntem düşünülebilir. Bir yöntem, işlevi değişebilen ve const karakter dizileri için de yüklemektir:

char[] parantezİçinde(char[] söz) {
    return '(' ~ söz ~ ')';
}

const(char)[] parantezİçinde(const(char)[] söz) {
    return '(' ~ söz ~ ')';
}

Bunun programcılıkta kaçınılması gereken kod tekrarı anlamına geldiğini görüyoruz. Bu işlevlerin ileride gelişebileceklerini veya olası hatalarının giderilebileceklerini düşünürsek, o değişikliklerin üçünde de yapılmasının unutulmaması gerekecektir. O yüzden bu riskli bir tasarımdır.

Başka bir yöntem, işlevi şablon olarak tanımlamaktır:

T parantezİçinde(T)(T söz) {
    return '(' ~ söz ~ ')';
}

O çözüm şablon içindeki kullanıma uyan her tür ile kullanılabilir. Bunun bazen fazla esnek olabileceğini ve şablon kısıtlamaları kullanılmasının gerekebileceğini de önceki bölümde görmüştük.

inout yöntemi şablon çözümüne çok benzer ama bütün türü değil, yalnızca türün const, immutable, veya değişebilen özelliğini esnek bırakır. O özelliği işlevin parametrelerinden otomatik olarak çıkarsar:

inout(char)[] parantezİçinde(inout(char)[] söz) {
    return '(' ~ söz ~ ')';
}

inout, parametreden otomatik olarak çıkarsanan özelliği dönüş türüne de aktarır.

O işlev char[] türüyle çağrıldığında hiç inout yazılmamış gibi derlenir. immutable(char)[] veya const(char)[] türleriyle çağrıldığında ise inout sırasıyla immutable veya const yerine geçer. Bunu işlevin dönüş türünü yazdırarak görebiliriz:

    char[] değişebilen;
    writeln(typeof(parantezİçinde(değişebilen)).stringof);

    const(char)[] sabit;
    writeln(typeof(parantezİçinde(sabit)).stringof);

    immutable(char)[] değişmez;
    writeln(typeof(parantezİçinde(değişmez)).stringof);

Üç çağrının farklı dönüş türleri:

char[]
const(char)[]
string

Özetle, inout parametre türünün const, immutable, veya değişebilen özelliğini dönüş türüne aktarır.

Davranış olanakları

pure, nothrow, ve @nogc işlevlerin davranışlarıyla ilgilidir.

pure işlevler

İşlevlerin değer üretebildiklerini ve yan etki oluşturabildiklerini İşlevler bölümünde görmüştük. Değer üretmenin yan etki oluşturmaktan daha yararlı olduğunun kabul edildiğini de o bölümde görmüştük.

Buna benzeyen başka yararlı bir kavram, bir işlevin saflığıdır. Bu kavramın D'deki tanımı fonksiyonel programlamadaki genel tanımından farklıdır: D'deki tanıma göre, dönüş değerini üretirken veya olası yan etkilerini oluştururken değişebilen evrensel veya static değerlere erişmeyen bir işlev saftır. (Giriş çıkış aygıtları da değişebilen evrensel durum olarak kabul edildiklerinden saf işlevler giriş ve çıkış işlemleri de içeremezler.)

Bir başka deyişle, dönüş değeri ve yan etkileri yalnızca parametrelerine, yerel değişkenlerine, ve değişmez evrensel değerlere bağlı olan bir işlev saftır.

Dolayısıyla, saflığın D'deki önemli farkı saf işlevlerin parametrelerini değiştirebilmeleridir.

Ek olarak, programın genel durumunu etkilediği için aslında izin verilmemesi gereken bazı işlemlere de D'nin bir sistem dili olması nedeniyle izin verilir. Bu yüzden, D'de saf işlevler aşağıdaki işlemleri de gerçekleştirebilirler:

pure anahtar sözcüğü bir işlevin bu koşullara uyduğunu bildirir ve bu durumun derleyici tarafından denetlenmesini sağlar.

Doğal olarak, aynı garantileri vermediklerinden saf olmayan işlevler saf işlevler tarafından çağrılamazlar.

Aşağıdaki örnek program bu koşullardan bazılarını gösteriyor:

import std.stdio;
import std.exception;

int değişebilenEvrensel;
const int constEvrensel;
immutable int immutableEvrensel;

void safOlmayanİşlev() {
}

int safİşlev(ref int i, int[] dilim) pure {
    // Hata atabilir:
    enforce(dilim.length >= 1);

    // Parametrelerini değiştirebilir:
    i = 42;
    dilim[0] = 43;

    // Değişmez evrensel duruma erişebilir:
    i = constEvrensel;
    i = immutableEvrensel;

    // new ifadesini kullanabilir:
    auto p = new int;

    // Değişebilen evrensel duruma erişemez:
    i = değişebilenEvrensel;    // ← derleme HATASI

    // Giriş ve çıkış işlemleri uygulayamaz:
    writeln(i);                 // ← derleme HATASI

    static int değişebilenStatik;

    // Değişebilen statik duruma erişemez:
    i = değişebilenStatik;      // ← derleme HATASI

    // Saf olmayan işlevleri çağıramaz:
    safOlmayanİşlev();          // ← derleme HATASI

    return 0;
}

void main() {
    int i;
    int[] dilim = [ 1 ];
    safİşlev(i, dilim);
}

Bazı saf işlevler parametrelerinde değişiklik de yapmazlar. Dolayısıyla, bu gibi işlevlerin programdaki tek etkileri dönüş değerleridir. Bu açıdan bakıldığında, parametrelerinde değişiklik yapmayan saf işlevlerin belirli parametre değerlerine karşılık hep aynı değeri döndürecekleri belli demektir. Dolayısıyla, böyle bir işlevin dönüş değeri eniyileştirme amacıyla sonradan kullanılmak üzere saklanabilir. (Bu gözlem hem derleyici hem de programcı için yararlıdır.)

Bir şablonun tam olarak ne çeşit kodlar içereceği ve derleneceği şablon parametrelerine bağlı olduğundan, saf olup olmadığı da şablon parametrelerine bağlı olabilir. Bu yüzden, şablonların saf olup olmadıkları derleyici tarafından otomatik olarak belirlenir. Şablonlarda gerekmese de pure anahtar sözcüğü istendiğinde yine de belirtilebilir. Benzer biçimde, auto işlevlerin saflıkları da otomatik olarak çıkarsanır.

Örneğin, aşağıdaki şablon N'nin sıfır olduğu durumda saf olmadığından şablon!0'ın saf olan bir işlev tarafından çağrılması mümkün değildir:

import std.stdio;

// Bu şablon N sıfır olduğunda saf değildir.
void şablon(size_t N)() {
    static if (N == 0) {
        // N sıfır olduğunda çıkışa yazdırmaya çalışmaktadır:
        writeln("sıfır");
    }
}

void foo() pure {
    şablon!0();    // ← derleme HATASI
}

void main() {
    foo();
}

Derleyici yukarıdaki şablonun 0 kullanımının saf olmadığını otomatik olarak anlar ve onun saf olan foo tarafından çağrılmasına izin vermez:

Error: pure function 'deneme.foo' cannot call impure function
'deneme.şablon!0.şablon'

Öte yandan, şablonun örneğin 1 değeri ile kullanımı saftır ve kod derlenir:

void foo() pure {
    şablon!1();    // ← derlenir
}

writeln gibi işlevlerin evrensel durumu etkiledikleri için kullanılamadıklarını gördük. Bu gibi kısıtlamalar özellikle hata ayıklama gibi durumlarda büyük sıkıntıya neden olacaklarından debug olarak işaretlenmiş olan kodlara saf olmasalar bile pure işlevler içinde izin verilir:

import std.stdio;

debug size_t fooÇağrılmaSayacı;

void foo(int i) pure {
    debug ++fooÇağrılmaSayacı;

    if (i == 0) {
        debug writeln("i sıfır");
        i = 42;
    }

    // ...
}

void main() {
    foreach (i; 0..100) {
        if ((i % 10) == 0) {
            foo(i);
        }
    }

    debug writefln("foo %s kere çağrıldı", fooÇağrılmaSayacı);
}

Yukarıdaki koddaki saf işlev hem değişebilen evrensel bir değişkeni değiştirmekte hem de çıkışa bir mesaj yazdırmaktadır. Bunlara rağmen derlenebilmesinin nedeni, o işlemlerin debug olarak işaretlenmiş olmalarıdır.

Not: Hatırlarsanız, o deyimlerin etkinleşmesi için programın -debug seçeneği ile derlenmesi gerekir.

Üye işlevler de saf olabilirler. Saf olmayan bir üye işlevin alt sınıftaki tanımı saf olabilir ama bunun tersi doğru değildir. Bunların örneklerini aşağıdaki kodda görmekteyiz:

interface Arayüz {
    void foo() pure;    // Alt sınıflar foo'yu saf olarak
                        // tanımlamak zorundadırlar.

    void bar();         // Alt sınıflar bar'ı isterlerse saf
                        // olarak da tanımlayabilirler.
}

class Sınıf : Arayüz {
    void foo() pure {    // Gerektiği için pure
        // ...
    }

    void bar() pure {     // Gerekmediği halde pure
        // ...
    }
}

Temsilciler ve isimsiz işlevler de saf olabilirler. Hazır değer olarak tanımlandıklarında derleyici saf olup olmadıklarını otomatik olarak çıkarsar:

import std.stdio;

void foo(int delegate(double) pure temsilci) {
    int i = temsilci(1.5);
}

void main() {
    foo(a => 42);                // ← derlenir

    foo((a) {                    // ← derleme HATASI
            writeln("merhaba");
            return 42;
        });
}

Yukarıdaki koddaki foo, parametresinin saf bir temsilci olmasını gerektirmektedir. Derleyici a => 42 temsilcisinin saf olduğunu anlar ve birinci çağrıya izin verir. İkinci temsilci ise saf olmadığı için foo'ya parametre değeri olarak gönderilemez:

Error: function deneme.foo (int delegate(double) pure temsilci)
is not callable using argument types (void)
nothrow işlevler

D'nin hata düzeneğini Hata Yönetimi bölümünde görmüştük.

Her işlevin hangi durumda ne tür hata atacağı o işlevin belgesinde belirtilmelidir. Ancak, genel bir kural olarak her işlevin her türden hata atabileceği varsayılabilir.

Bazı durumlarda ise çağırdığımız işlevlerin ne tür hatalar atabildiklerini değil, kesinlikle hata atmadıklarını bilmek isteriz. Örneğin, belirli adımlarının kesintisiz olarak devam etmesi gereken bir algoritma o adımlar sırasında hata atılmadığından emin olmak isteyebilir.

nothrow, işlevin hata atmadığını garanti eder:

int topla(int birinci, int ikinci) nothrow {
    // ...
}

Not: Hatırlarsanız, "giderilemez derecede hatalı" durumları ifade eden Error sıradüzeni altındaki hataların yakalanmaları önerilmez. Burada bir işlevin hata atmadığından söz edilirken o işlevin "Exception sıradüzeni altındaki hatalardan atmadığı" kastediliyor. Yoksa, nothrow işlevler Error sıradüzeni altındaki hataları atabilirler.

Yukarıdaki işlev ne kendisi hata atabilir ne de hata atabilen bir işlevi çağırabilir:

int topla(int birinci, int ikinci) nothrow {
    writeln("topluyorum");      // ← derleme HATASI
    return birinci + ikinci;
}

Derleme hatası, topla'nın bu koşula uymadığını bildirir:

Error: function deneme.topla 'topla' is nothrow yet may throw

Bunun nedeni, writeln'in nothrow olarak bildirilmiş bir işlev olmamasıdır.

Derleyici, işlevlerin kesinlikle hata atmayacaklarını da anlayabilir. topla'nın aşağıdaki tanımında her tür hata yakalandığından nothrow'un getirdiği garantiler geçerliliğini sürdürür ve işlev nothrow olmayan işlevleri bile çağırabilir:

int topla(int birinci, int ikinci) nothrow {
    int sonuç;

    try {
        writeln("topluyorum");   // ← derlenir
        sonuç = birinci + ikinci;

    } catch (Exception hata) {   // bütün hataları yakalar
        // ...
    }

    return sonuç;
}

Yukarıda belirtildiği gibi, nothrow belirteci Error sıradüzeni altındaki hataları kapsamaz. Örneğin, dilim elemanına [] işleci ile erişilirken RangeError atılabileceği halde aşağıdaki işlev yine de nothrow olarak tanımlanabilir:

int foo(int[] arr, size_t i) nothrow {
    return 10 * arr[i];
}

pure'da olduğu gibi, şablonların, temsilcilerin, isimsiz işlevlerin, ve auto işlevlerin hata atıp atmadıkları otomatik olarak çıkarsanır.

@nogc işlevler

D çöp toplayıcılı bir dildir. Çoğu programda kullanılan çok sayıda değişken ve algoritma, çöp toplayıcıya ait olan dinamik bellek bölgelerini kullanır. Programda kullanımları sona eren dinamik bellek bölgeleri daha sonra çöp toplama adı verilen bir algoritma ile otomatik olarak sonlandırılır.

Sık kullanılan bazı D olanakları da çöp toplayıcıdan yararlanır. Örneğin, dizi elemanları dinamik bellek bölgelerinde yaşarlar:

// Dolaylı olarak çöp toplayıcıdan yararlanan bir işlev
int[] ekle(int[] dizi) {
    dizi ~= 42;
    return dizi;
}

Yukarıdaki ~= işleci de mevcut sığa yetersiz olduğunda çöp toplayıcıdan yeni bir bellek bölgesi ayırır.

Hem veri yapıları hem de algoritmalar açısından büyük kolaylık getirmelerine rağmen, bellek ayırma ve çöp toplama işlemleri program hızını farkedilir derecede yavaşlatabilir.

"No garbage collector operations"ın kısaltması olan ve "çöp toplayıcı işlemleri içermez" anlamına gelen @nogc, işlevlerin hız kaybına neden olabilecek böyle işlemler içermediğini garanti etmek içindir:

void foo() @nogc {
    // ...
}

Derleyici bu garantiyi denetler. Örneğin, aşağıdaki işlev yukarıdaki ekle() işlevini çağıramaz çünkü ekle() bu garantiyi vermemektedir:

void foo() @nogc {
    int[] dizi;
    // ...
    ekle(dizi);    // ← derleme HATASI
}
Error: @nogc function 'deneme.foo' cannot call non-@nogc function
'deneme.ekle'
Güvenilirlik olanakları

@safe, @trusted, ve @system belirteçleri işlevlerin güvenilirlikleri ile ilgilidir. Yine pure'da olduğu gibi, şablonların, temsilcilerin, isimsiz işlevlerin, ve auto işlevlerin güvenilirlikleri otomatik olarak çıkarsanır.

@safe işlevler

Programcı hatalarının önemli bir bölümü farkında olmadan belleğin yanlış yerlerine yazılması ve o yerlerdeki bilgilerin bu yüzden bozulmaları ile ilgilidir. Bu hatalar genellikle göstergelerin yanlış kullanılmaları ve güvensiz tür dönüşümleri sonucunda oluşur.

"Güvenli" anlamına gelen @safe işlevler, belleği bozmayacağını garanti eden işlevlerdir.

Bazılarına bu bölümlerde hiç değinmemiş olsam da derleyici @safe işlevlerde aşağıdaki işlemlere ve olanaklara izin vermez:

@trusted işlevler

"Güvenilir" anlamına gelen @trusted olarak bildirilmiş olan işlevler @safe olarak bildirilemeyecek oldukları halde tanımsız davranışa neden olmayan işlevlerdir.

Böyle işlevler @safe işlevlerin yasakladığı işlemleri yapıyor oldukları halde hatalı olmadıkları programcı tarafından garantilenen işlevlerdir. Programcının derleyiciye "bu işleve güvenebilirsin" demesi gibidir.

Derleyici programcının sözüne güvenir ve @trusted işlevlerin @safe işlevlerden çağrılmalarına izin verir.

@system işlevler

@safe veya @trusted olarak bildirilmiş olmayan bütün işlevlerin @system oldukları varsayılır. Derleyici böyle işlevlerin doğru veya güvenilir olduklarını düşünemez.

Derleme zamanında işlev işletme (CTFE)

Derleme zamanında yapılabilen hesaplar çoğu programlama dilinde oldukça kısıtlıdır. Bu hesaplar genellikle sabit uzunluklu dizilerin uzunlukları veya hazır değerler kullanan aritmetik işlemler kadar basittir:

    writeln(1 + 2);

Yukarıdaki 1 + 2 işlemi derleme zamanında işletilir ve program doğrudan writeln(3) yazılmış gibi derlenir; o hesap için çalışma zamanında hiç süre harcanmaz.

D'nin "compile time function execution (CTFE)" denen özelliği ise normal olarak çalışma zamanında işletildiklerini düşüneceğimiz işlevlerin bile derleme zamanında işletilmelerini sağlar.

Çıktıya bir menü yazdıran bir programa bakalım:

import std.stdio;
import std.string;
import std.range;

string menüSatırları(string[] seçenekler) {
    string sonuç;

    foreach (i, seçenek; seçenekler) {
        sonuç ~= format(" %s. %s\n", i + 1, seçenek);
    }

    return sonuç;
}

string menü(string başlık, string[] seçenekler,
            size_t genişlik) {
    return format("%s\n%s\n%s",
                  başlık.center(genişlik),
                  '='.repeat(genişlik),    // yatay çizgi
                  menüSatırları(seçenekler));
}

void main() {
    enum tatlıMenüsü =
        menü("Tatlılar",
             [ "Baklava", "Kadayıf", "Muhallebi" ], 20);

    writeln(tatlıMenüsü);
}

Aynı iş çok farklı başka yollarla da yapılabilse de, yukarıdaki program bazı işlemler sonucunda bir dizgi üretmekte ve bu dizgiyi çıktıya yazdırmaktadır:

      Tatlılar      
====================
 1. Baklava
 2. Kadayıf
 3. Muhallebi

tatlıMenüsü değişkeni enum olarak tanımlandığından değerinin derleme zamanında bilinmesi gerektiğini biliyoruz. Bu, menü işlevinin derleme zamanında işletilmesi için yeterlidir ve döndürdüğü değer tatlıMenüsü'nü ilklemek için kullanılır. Sonuçta, tatlıMenüsü değişkeni sanki o işlemlerin sonucunda oluşan dizgi programa açıkça yazılmış gibi derlenir:

    // Yukarıdaki kodun eşdeğeri:
    enum tatlıMenüsü = "      Tatlılar      \n"
                       "====================\n"
                       " 1. Baklava\n"
                       " 2. Kadayıf\n"
                       " 3. Muhallebi\n";

Bir işlevin derleme zamanında işletilmesi için bunun gerektiği bir ifadede geçmesi yeterlidir:

Her işlev derleme zamanında işletilemez. Örneğin, evrensel bir değişkene erişen bir işlev o değişken ancak çalışma zamanında yaşamaya başlayacağından derleme zamanında işletilemez. Benzer biçimde, stdout da çalışma zamanında yaşamaya başlayacağından çıkışa yazdıran bir işlev de derleme zamanında işletilemez.

__ctfe değişkeni

CTFE'nin güçlü bir tarafı, aynı işlevin hem çalışma zamanında hem de derleme zamanında kullanılabilmesidir. İşlevin bunun için farklı yazılması gerekmese de bazı ifadelerin ancak çalışma zamanında etkinleştirilmeleri gerekebilir. Bunun için __ctfe değişkeninden yararlanılır: Bu değişken işlev derleme zamanında işletilirken true, çalışma zamanında işletilirken false değerindedir:

import std.stdio;

size_t sayaç;

int foo() {
    if (!__ctfe) {
        // Çalışma zamanında işletilmekteyiz
        ++sayaç;
    }

    return 42;
}

void main() {
    enum i = foo();
    auto j = foo();
    writefln("foo %s kere çağrıldı.", sayaç);
}

sayaç değişkeninin derleme zamanında arttırılması mümkün olmadığından yukarıdaki program onu yalnızca çalışma zamanında işletildiğinde arttırmaktadır. i derleme zamanında ve j çalışma zamanında ilklendiklerinden foo çalışma zamanında 1 kere çağrılmaktadır:

foo 1 kere çağrıldı.
Özet