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

bağlanım: [linkage], derlenen koddaki özgün ismi belirleyen kurallar vs.
bağlayıcı: [linker], derleyicinin oluşturduğu program parçalarını bir araya getiren program
derleyici: [compiler], programlama dili kodunu bilgisayarın anladığı makine koduna çeviren program
emekliye ayrılan: [deprecated], hâlâ kullanılan ama yakında geçersiz olacak olanak
ilinti: [binding], yabancı kütüphanenin olanaklarını D söz dizimiyle bildiren dosya
iş parçacığı: [thread], işletim sisteminin program işletme birimi
kaynak dosya: [source file], programcının yazdığı kodu içeren dosya
klasör: [directory], dosyaları barındıran dosya sistemi yapısı, "dizin"
kütüphane: [library], belirli bir konuda çözüm getiren tür tanımlarının ve işlevlerin bir araya gelmesi
modül: [module], programın veya kütüphanenin işlev ve tür tanımlarından oluşan bir alt birimi
özgün isim üretme: [name mangling], bağlayıcı tanıyabilsin diye programdaki isimlerin özgünleştirilmeleri
paket: [package], aynı klasörde bulunan modüller
Phobos: [Phobos], D dilinin standart kütüphanesi
takma isim: [alias], var olan bir olanağın başka bir ismi
tanım: [definition], bir ismin neyi ifade ettiğinin belirtilmesi
... bütün sözlük



İngilizce Kaynaklar


Diğer




Modüller ve Kütüphaneler

D programlarını ve kütüphanelerini oluşturan en alt yapısal birimler modüllerdir.

D'nin modül kavramı çok basit bir temel üzerine kuruludur: Her kaynak dosya bir modüldür. Bu tanıma göre, şimdiye kadar deneme programlarımızı yazdığımız tek kaynak dosya bile bir modüldür.

Her modülün ismi, dosya isminin .d uzantısından önceki bölümü ile aynıdır ve kaynak dosyanın en başına yazılan module anahtar sözcüğü ile belirtilir. Örneğin, "kedi.d" isimli bir kaynak dosyanın modül ismi aşağıdaki gibi belirtilir:

module kedi;

class Kedi {
    // ...
}

Eğer modül bir pakedin parçası değilse module satırı isteğe bağlıdır. (Paketleri biraz aşağıda göreceğiz.) Yazılmadığı zaman otomatik olarak dosyanın isminin .d'den önceki bölümü kullanılır.

static this() ve static ~this()

Modül düzeyinde tanımlanan static this() ve static ~this(), yapı ve sınıflardaki eşdeğerleri ile aynı anlamdadır:

module kedi;

static this() {
    // ... modülün ilk işlemleri ...
}

static ~this() {
    // ... modülün son işlemleri ...
}

Bu işlevler her iş parçacığında ayrı ayrı işletilir. (Çoğu program yalnızca main()'in işlediği tek iş parçacığından oluşur.) İş parçacıklarının sayısından bağımsız olarak bütün programda tek kere işletilmesi gereken kodlar ise (örneğin, shared ve immutable değişkenlerin ilklenmeleri) shared static this() ve shared static ~this() işlevlerinde tanımlanırlar. Bunları daha sonraki Veri Paylaşarak Eş Zamanlı Programlama bölümünde göreceğiz.

Dosya ve modül isimleri

D programlarını Unicode olarak oluşturma konusunda şanslıyız; bu, hangi ortamda olursa olsun geçerlidir. Ancak, dosya sistemleri konusunda aynı serbesti bulunmaz. Örneğin, Windows işletim sistemlerinin standart dosya sistemleri dosya isimlerinde büyük/küçük harf ayrımı gözetmezken, Linux sistemlerinde büyük/küçük harfler farklıdır. Ayrıca, çoğu dosya sistemi dosya isimlerinde kullanılabilecek karakterler konusunda kısıtlamalar getirir.

O yüzden, programlarınızın taşınabilir olmaları için dosya isimlerinde yalnızca ASCII küçük harfler kullanmanızı öneririm. Örneğin, yukarıdaki Kedi sınıfı ile birlikte kullanılacak olan bir Köpek sınıfının modülünün dosya ismini "kopek.d" olarak seçebiliriz.

Dolayısıyla, modülün ismi de ASCII harflerden oluşur:

module kopek;    // ASCII harflerden oluşan modül ismi

class Köpek {    // Unicode harflerden oluşan program kodu
    // ...
}
Paketler

Modüllerin bir araya gelerek oluşturdukları yapıya paket denir. D'nin paket kavramı da çok basittir: Dosya sisteminde aynı klasörde bulunan bütün modüller aynı pakedin parçası olarak kabul edilirler. Pakedi içeren klasörün ismi de pakedin ismi haline gelir ve modül isimlerinin baş tarafını oluşturur.

Örneğin, yukarıdaki "kedi.d" ve "kopek.d" dosyalarının "hayvan" isminde bir klasörde bulunduklarını düşünürsek, modül isimlerinin başına klasör ismini yazmak, onları aynı pakedin modülleri yapmaya yeter:

module hayvan.kedi;

class Kedi {
    // ...
}

Aynı şekilde kopek modülü için de:

module hayvan.kopek;

class Köpek {
    // ...
}

module satırı bir pakedin parçası olan modüllerde zorunludur.

Paket isimleri dosya sistemi klasörlerine karşılık geldiğinden, iç içe klasörlerde bulunan modüllerin paket isimleri de o klasör yapısının eşdeğeridir. Örneğin, "hayvan" klasörünün altında bir de "omurgalilar" klasörü olsa, oradaki bir modülün paket ismi bu klasörü de içerir:

module hayvan.omurgalilar.kedi;

Kaynak dosyaların ne derece dallanacağı programın büyüklüğüne ve tasarımına bağlıdır. Küçük bir programın bütün dosyalarının tek bir klasörde bulunmasında bir sakınca yoktur. Öte yandan, dosyaları belirli bir düzen altına almak için klasörleri gerektiği kadar dallandırmak da mümkündür.

Modüllerin programda kullanılmaları

Şimdiye kadar çok kullandığımız import anahtar sözcüğü, bir modülün başka bir modüle tanıtılmasını ve o modül içinde kullanılabilmesini sağlar:

import std.stdio;

import'tan sonra yazılan modül ismi, eğer varsa paket bilgisini de içerir. Yukarıdaki koddaki std., standart kütüphaneyi oluşturan modüllerin std isimli pakette bulunduklarını gösterir.

Benzer şekilde, hayvan.kedi ve hayvan.kopek modülleri bir "deneme.d" dosyasında şu şekilde bildirilir:

module deneme;          // bu modülün ismi

import hayvan.kedi;     // kullandığı bir modül
import hayvan.kopek;    // kullandığı başka bir modül

void main() {
    auto kedi = new Kedi();
    auto köpek = new Köpek();
}

Not: Aşağıda anlatıldığı gibi, yukarıdaki programın derlenip oluşturulabilmesi için o modül dosyalarının da bağlayıcıya derleme satırında bildirilmeleri gerekir.

Birden fazla modül aynı anda eklenebilir:

import hayvan.kedi, hayvan.kopek;
Seçerek eklemek

Bir modüldeki isimlerin hepsini birden eklemek yerine içindeki isimler tek tek seçilerek eklenebilir:

import std.stdio : writeln;

// ...

    writefln("Merhaba %s.", isim);    // ← derleme HATASI

stdio modülünden yalnızca writeln eklenmiş olduğundan yukarıdaki kod derlenemez (writefln eklenmemiştir).

İsimleri seçerek eklemek hepsini birden eklemekten daha iyidir çünkü isim çakışmalarının olasılığı daha azdır. Biraz aşağıda bir örneğini göreceğimiz gibi, isim çakışması iki farklı modüldeki aynı ismin eklenmesiyle oluşur.

Ek olarak, yalnızca belirtilen isimler derleneceğinden derleme sürelerinin kısalacağı da beklenebilir. Öte yandan, her kullanılan ismin ayrıca belirtilmesini gerektirdiğinden seçerek eklemek daha fazla emek gerektirir.

Kod örneklerini kısa tutmak amacıyla bu kitapta seçerek ekleme olanağından yararlanılmamaktadır.

Yerel import satırları

Bu kitaptaki bütün import satırlarını hep programların en başlarına yazdık:

import std.stdio;     // ← en başta
import std.string;    // ← en başta

// ... modülün geri kalanı ...

Aslında modüller herhangi başka bir satırda da eklenebilirler. Örneğin, aşağıdaki programdaki iki işlev ihtiyaç duydukları farklı modülleri kendi yerel kapsamlarında eklemekteler:

string mesajOluştur(string isim) {
    import std.string;

    string söz = format("Merhaba %s", isim);
    return söz;
}

void kullanıcıylaEtkileş() {
    import std.stdio;

    write("Lütfen isminizi girin: ");
    string isim = readln();
    writeln(mesajOluştur(isim));
}

void main() {
    kullanıcıylaEtkileş();
}

import satırlarının yerel kapsamlarda bulunmaları modül kapsamında bulunmalarından daha iyidir çünkü derleyici kullanılmayan kapsamlardaki import satırlarını derlemek zorunda kalmaz. Ek olarak, yerel olarak eklenmiş olan modüllerdeki isimler ancak eklendikleri kapsamda görünürler ve böylece isim çakışmalarının olasılığı da azalmış olur.

Daha sonra Katmalar bölümünde göreceğimiz şablon katmaları olanağında modüllerin yerel olarak eklenmeleri şarttır.

Bu kitaptaki örnekler yerel import olanağından hemen hemen hiç yararlanmazlar çünkü bu olanak D'ye bu kitabın yazılmaya başlanmasından sonra eklenmiştir.

Modüllerin dosya sistemindeki yerleri

Modül isimleri dosya sistemindeki dosyalara bire bir karşılık geldiğinden, derleyici bir modül dosyasının nerede bulunduğunu modül ismini klasör ve dosya isimlerine dönüştürerek bulur.

Örneğin, yukarıdaki programın kullandığı iki modül, "hayvan/kedi.d" ve "hayvan/kopek.d" dosyalarıdır. Dolayısıyla, yukarıdaki dosyayı da sayarsak bu programı oluşturmak için üç modül kullanılmaktadır.

Kısa ve uzun isimler

Programda kullanılan isimler, paket ve modül bilgilerini de içeren uzun halde de yazılabilirler. Bunu Kedi sınıfının tür ismini kısa ve uzun yazarak şöyle gösterebiliriz:

    auto kedi0 = new Kedi();
    auto kedi1 = new hayvan.kedi.Kedi();  // üsttekinin aynısı

Normalde uzun isimleri kullanmak gerekmez. Onları yalnızca olası karışıklıkları gidermek için kullanırız. Örneğin, iki modülde birden tanımlanmış olan bir ismi kısa olarak yazdığımızda derleyici hangi modüldekinden bahsettiğimizi anlayamaz.

Hem hayvan modülünde hem de arabalar modülünde bulunabilecek Jaguar isimli iki sınıftan hangisinden bahsettiğimizi uzun ismiyle şöyle belirtmek zorunda kalırız:

import hayvan.jaguar;
import arabalar.jaguar;

// ...

    auto karışıklık =  Jaguar();            // ← derleme HATASI

    auto hayvanım = hayvan.jaguar.Jaguar(); // ← derlenir
    auto arabam = arabalar.jaguar.Jaguar(); // ← derlenir
Takma isimle eklemek

Modüller kolaylık veya isim çakışmalarını önleme gibi amaçlarla takma isim vererek eklenebilirler:

import etobur = hayvan.jaguar;
import araç = arabalar.jaguar;

// ...

    auto hayvanım = etobur.Jaguar();       // ← derlenir
    auto arabam   = araç.Jaguar();         // ← derlenir

Bütün modüle takma isim vermek yerine seçilen her isme ayrı ayrı takma isim de verilebilir.

Bir örnek olarak, aşağıdaki kod -w derleyici seçeneği ile derlendiğinde derleyici .sort niteliğinin değil, sort() işlevinin yeğlenmesi yönünde bir uyarı verir:

import std.stdio;
import std.algorithm;

// ...

    auto dizi = [ 2, 10, 1, 5 ];
    dizi.sort;    // ← derleme UYARISI
    writeln(dizi);
Warning: use std.algorithm.sort instead of .sort property

Not: Yukarıdaki dizi.sort ifadesi sort(dizi) çağrısının eşdeğiridir. Farkı, ilerideki bir bölümde göreceğimiz UFCS söz dizimi ile yazılmış olmasıdır.

Bu durumda bir çözüm, std.algorithm.sort işlevinin takma isimle eklenmesidir. Aşağıdaki yeni algSort ismi sort() işlevi anlamına geldiğinden derleyici uyarısına gerek kalmamış olur:

import std.stdio;
import std.algorithm : algSort = sort;

void main() {
    auto arr = [ 2, 10, 1, 5 ];
    arr.algSort;
    writeln(arr);
}
Pakedi modül olarak eklemek

Bazen bir paketteki bir modül eklendiğinde o pakedin başka modüllerinin de eklenmeleri gerekiyor olabilir. Örneğin, hayvan.kedi modülü eklendiğinde hayvan.kopek, hayvan.at, vs. modülleri de ekleniyordur.

Böyle durumlarda modülleri tek tek eklemek yerine bütün pakedi veya bir bölümünü eklemek mümkündür:

import hayvan;    // ← bütün paket modül gibi ekleniyor

Bu, ismi package.d olan özel bir ayar dosyası ile sağlanır. Bu dosyada önce bir module satırıyla pakedin ismi bildirilir, sonra da bir arada eklenmeleri gereken modüller public olarak eklenirler:

// hayvan/package.d dosyasının içeriği:
module hayvan;

public import hayvan.kedi;
public import hayvan.kopek;
public import hayvan.at;
// ... diğer modüller için de benzer satırlar ...

Bir modülün public olarak eklenmesi, kullanıcıların eklenen modüldeki isimleri görebilmelerini sağlar. Sonuç olarak, kullanıcılar aslında bir paket olan hayvan modülünü eklediklerinde hayvan.kedi, hayvan.kopek, vs. modüllere otomatik olarak erişmiş olurlar.

Modül olanaklarını emekliye ayırmak

Modüller geliştikçe yeni sürümleri kullanıma sunulur. Modülün yazarları bazı olanakların belirli bir sürümden sonra emekliye ayrılmalarına karar vermiş olabilirler. Bir olanağın emekliye ayrılması, yeni yazılacak olan programların artık o olanağı kullanmamaları gerektiği anlamına gelir. Emekliye ayrılan bir olanak daha ilerideki bir sürümde modülden çıkartılabilir bile.

Olanakların emekliye ayrılmalarını gerektiren çeşitli nedenler vardır. Örneğin, modülün yeni sürümü o olanağın yerine kullanılabilecek daha iyi bir olanak getiriyordur, olanak başka bir modüle taşınmıştır, olanağın ismi modülün geri kalanıyla uyumlu olsun diye değiştirilmiştir, vs.

Bir olanağın emekliye ayrılmış olduğu deprecated anahtar sözcüğü ile ve gerekiyorsa özel bir mesajla bildirilir. Örneğin, aşağıdaki mesaj, bir_şey_yap() işlevini kullananlara işlevin isminin değiştiğini belirtmektedir:

deprecated("Lütfen bunun yerine birŞeyYap() işlevini kullanınız.")
void bir_şey_yap() {
    // ...
}

Emekliye ayrılan olanak kullanıldığında derleyicinin nasıl davranacağı aşağıdaki derleyici seçenekleri ile ayarlanabilir:

Örneğin, emekliye ayrılmış olan yukarıdaki olanağı kullanan bir program -de seçeneği ile derlendiğinde derleme hatası oluşur:

    bir_şey_yap();
$ dmd deneme.d -de
deneme.d: Deprecation: function deneme.bir_şey_yap is
deprecated - Lütfen bunun yerine birŞeyYap() işlevini kullanınız.

Çoğu durumda, emekliye ayrılan olanak yeni olanağın takma ismi olarak tanımlanır:

deprecated("Lütfen bunun yerine birŞeyYap() işlevini kullanınız.")
alias bir_şey_yap = birŞeyYap;

void birŞeyYap() {
    // ...
}

alias anahtar sözcüğünü ilerideki bir bölümde göreceğiz.

Modüllerdeki tanımların programa dahil edilmesi

import anahtar sözcüğü, belirtilen modülün programın parçası haline gelmesi için yeterli değildir. import, yalnızca o modüldeki olanakların bu kaynak kod içinde kullanılabilmelerini sağlar. O kadarı ancak kaynak kodun derlenebilmesi için yeterlidir.

Yukarıdaki programı yalnızca "deneme.d" dosyasını kullanarak oluşturmaya çalışmak yetmez:

$ dmd deneme.d -w -de
deneme.o: In function `_Dmain':
deneme.d: undefined reference to `_D6hayvan4kedi4Kedi7__ClassZ'
deneme.d: undefined reference to `_D6hayvan5kopek6Köpek7__ClassZ'
collect2: ld returned 1 exit status

O hata mesajları bağlayıcıdan gelir. Her ne kadar anlaşılmaz isimler içeriyor olsalar da, yukarıdaki hata mesajları programda kullanılan bazı tanımların bulunamadıklarını bildirir.

Programın oluşturulması, perde arkasında çağrılan bağlayıcının görevidir. Derleyici, derlediği modülleri bağlayıcıya verir; program, bağlayıcının bir araya getirdiği parçalardan oluşturulur.

O yüzden, programı oluşturan bütün parçaların derleme satırında belirtilmeleri gerekir. Yukarıdaki programın oluşturulabilmesi için, kullandığı "hayvan/kedi.d" ve "hayvan/kopek.d" dosyaları da derleme satırında bildirilmelidir:

$ dmd deneme.d hayvan/kedi.d hayvan/kopek.d -w -de

Modülleri derleme satırında her program için ayrı ayrı belirtmek yerine kütüphaneler içinden de kullanabiliriz.

Kütüphaneler

Modül tanımlarının derlendikten sonra bir araya getirilmelerine kütüphane adı verilir. Kütüphaneler kendileri program olmadıklarından, programların başlangıç işlevi olan main kütüphanelerde bulunmaz. Kütüphaneler yalnızca işlev, yapı, sınıf, vs. tanımlarını bir araya getirirler. Daha sonra program oluşturulurken programın diğer modülleriyle bağlanırlar.

Kütüphane oluşturmak için dmd'nin -lib seçeneği kullanılır. Oluşturulan kütüphanenin isminin hayvan olacağını da -of seçeneği ile bildirirsek, yukarıdaki "kedi.d" ve "kopek.d" modüllerini içeren bir kütüphane şu şekilde oluşturulabilir:

$ dmd hayvan/kedi.d hayvan/kopek.d -lib -ofhayvan -w -de

Konsoldan çalıştırılan o komut, belirtilen .d dosyalarını derler ve bir kütüphane dosyası olarak bir araya getirir. Çalıştığınız ortama bağlı olarak kütüphane dosyasının ismi farklı olacaktır. Örneğin, Linux ortamlarında kütüphane dosyalarının uzantıları .a olur: hayvan.a.

Program oluşturulurken artık "hayvan/kedi.d"nin ve "hayvan/kopek.d"nin ayrı ayrı bildirilmelerine gerek kalmaz. Onları içeren kütüphane dosyası tek başına yeterlidir:

$ dmd deneme.d hayvan.a -w -de

O komut, daha önce kullandığımız şu komutun eşdeğeridir:

$ dmd deneme.d hayvan/kedi.d hayvan/kopek.d -w -de

Bir istisna olarak, şimdiye kadar çok yararlandığımız Phobos modüllerini içeren standart kütüphanenin açıkça bildirilmesi gerekmez. O kütüphane, programa otomatik olarak dahil edilir. Yoksa normalde onu da örneğin şu şekilde belirtmemiz gerekirdi:

$ dmd deneme.d hayvan.a /usr/lib64/libphobos2.a -w -de

Not: Phobos kütüphane dosyasının yeri ve ismi sizin ortamınızda farklı olabilir.

Başka dillerin kütüphanelerini kullanmak

C ve C++ gibi başka bazı derlemeli dillerin kütüphaneleri D programlarında kullanılabilir. Ancak, farklı diller farklı bağlanım kullandıklarından, böyle bir kütüphanenin D ile kullanılabilmesi için o kütüphanenin bir D ilintisinin olması gerekir.

Bağlanım, bir kütüphanenin olanaklarının dışarıdan erişimini ve o olanakların isimlerinin (sembollerinin) derlenmiş kodda nasıl ifade edildiklerini belirleyen kurallar bütünüdür. Derlenmiş koddaki isimler programcının kaynak kodda yazdığı isimlerden farklıdır: Derlenmiş koddaki isimler belirli bir dilin veya bir derleyicinin kurallarına göre özgünleştirilmişlerdir.

Örneğin, ismi kaynak kodda foo olan bir işlevin derlenmiş koddaki özgün ismi, C bağlanım kurallarına göre başına alt çizgi karakteri eklenerek oluşturulur: _foo. Özgün isim üretme C++ ve D dillerinde daha karmaşıktır çünkü bu diller aynı ismin farklı modüllerde, yapılarda, sınıflarda, ve bir işlevin farklı yüklemelerinde kullanılmasına izin verir. D kaynak kodundaki foo gibi bir işlevin özgün ismi onu olası bütün başka foo isimlerinden ayırt edecek biçimde seçilir. Özgün isimlerin tam olarak ne oldukları genelde programcı için önemli olmasa da, bu konuda core.demangle modülünden yararlanılabilir:

module deneme;

import std.stdio;
import core.demangle;

void foo() {
}

void main() {
    writeln(mangle!(typeof(foo))("deneme.foo"));
}

Not: Söz dizimi kitabın bu noktasında yabancı gelen mangle bir işlev şablonudur. Şablonları daha sonra Şablonlar bölümünde göreceğiz.

Programın çıktısı, yukarıdaki foo ile aynı türden olan deneme.foo isimli bir işlevin özgün ismini göstermektedir:

_D6deneme3fooFZv

Bağlayıcının verdiği hata mesajlarının anlaşılmaz isimler içermelerinin nedeni de özgün isimlerdir. Örneğin, yukarıdaki bir bağlayıcı hata mesajında hayvan.kedi.Kedi ismi değil, _D6hayvan4kedi4Kedi7__ClassZ ismi geçmiştir.

extern() niteliği olanakların bağlanımlarını belirtmek için kullanılır. extern() ile kullanılabilen bağlanım türleri şunlardır: C, C++, D, Objective-C, Pascal, System, ve Windows. Örneğin, bir C kütüphanesinde tanımlanmış olan bir işlevi çağırması gereken bir D kodunun o işlevi C bağlanımı ile bildirmesi gerekir:

// 'foo'nun C bağlanımı olduğu bildiriliyor (örneğin, bir C
// kütüphanesinde tanımlanmıştır)
extern(C) void foo();

void main() {
    foo();  // bu işlev çağrısı '_foo' özgün ismi ile yapılır
}

C++'ın namespace anahtar sözcüğü ile tanımlanan isim alanları extern(C++)'ın ikinci parametresi olarak belirtilir. Örneğin, aşağıdaki bar() bildirimi, C++ kütüphanesindeki a::b::c::bar() işlevine karşılık gelir (dikkat ederseniz, D kodu :: yerine nokta kullanır):

// 'bar'ın a::b::c isim alanında bulunduğu ve C++ bağlanımı
// olduğu bildiriliyor:
extern(C++, a.b.c) void bar();

void main() {
    bar();          // a::b::c::bar()'a çağrıdır
    a.b.c.bar();    // üsttekinin eşdeğeri
}

Bir kütüphanedeki olanakların D bildirimlerini içeren dosyaya o kütüphanenin D ilintisi denir. D ilintilerini elle kendiniz yazmak yerine, çoğu yaygın kütüphanenin ilintilerini içeren Deimos projesinden yararlanmanızı öneririm.

Bağlanım türü belirtilmeden kullanılan extern niteliğinin farklı bir anlamı vardır: Bir değişken için kullanılan yerin başka bir kütüphanenin sorumluluğunda olduğunu bildirir. Farklı anlamlar taşıdıklarından, extern ve extern() nitelikleri birlikte kullanılabilir:

// 'g_degisken' için kullanılan yerin bir C kütüphanesi
// tarafından zaten ayrıldığı bildiriliyor:
extern(C) extern int g_degisken;

Yukarıdaki extern niteliği kullanılmasa, C bağlanımına sahip olmasından bağımsız olarak, g_degisken bu D modülünün bir değişkeni haline gelirdi.