Zig nedir ve nerede işe yarar?

Zig, bellek üzerinde açık denetim ve C ile doğrudan birlikte çalışabilirlik arayan programcılar için tasarlanmış bir sistem programlama dilidir. Dilin hedeflerinden biri, bellek ayırmanın gizli olmamasıdır. Bunu çoğunlukla API tasarımı sağlar: bellek ayıran işlevler genellikle ayırıcıyı çağıranın verdiği bir parametre olarak alır ve ayırma başarısız olabileceği için hata da dönüş türünde taşınır. Bu bir derleyici kuralı değil, bir tasarım geleneğidir; ilerideki newlineOffsets(allocator, data) örneği onu izler. Bu rehberde tek bir küçük araç yazacağız: satir-say. Bir dosyanın satır ve bayt sayısını yazan bu komut satırı programı, dilin temel kavramlarını, güncel G/Ç yöntemini, derleme sistemini ve C sınırını aynı kod üzerinde gösterecek.

Bu yazıyı hazırladığım 7 Ekim 2026'da güncel kararlı sürüm, resmî indirme dizinine göre 1 Ekim 2026 tarihli Zig 0.17.0; sürüm duyurusu 2 Ekim'de yapıldı. Bir önceki kararlı sürüm, 13 Nisan 2026 tarihli 0.16.0. Zig 1.0 henüz yayımlanmadı: 0.17.0 sürüm notları, 1.0'dan önce dil kararlılığının hâlâ gerektiğini söylüyor. Bu yüzden yazıdaki her API ayrıntısı 0.17.0 için geçerlidir ve 0.x sürümleri arasında değişebilir.

Eski eğitimleri kopyalamadan önce son iki sürümün neyi değiştirdiğini bilin. 0.16.0, eski std.io ve std.fs kalıplarının yerine, programa verilen bir std.Io değeri üzerinden yürüyen G/Ç modelini getirdi. 0.17.0'da derleme sistemi büyük ölçüde yeniden yazıldı ve yeni bir Build Server Protocol eklendi; sürüm notları ZLS desteğinin bu geçişte aksadığını belirtiyor, bu yüzden editör entegrasyonunu kurduğunuz Zig sürümüyle denemeden güvenmeyin. @cImport kaldırıldı ve DebugAllocator yerine SafeAllocator önerilir oldu. Dil referansı ayrıca async işlevlerin 0.11'de geri çekildiğini söylüyor; bu yüzden onları kararlı bir özellik olarak öğretmeyeceğim.

Zig'i başka dillerle yarıştırmak yerine kısıtlarınıza bakın. Mevcut bir C ABI'si ve araç zinciri ile elle yönetilen kaynak politikası işin merkezindeyse C doğal bir adaydır ve Zig aynı sınırda onunla birlikte çalışabilir. Derleme zamanında sahiplik denetimi çekirdek bir gereksinimse Rust'ın sahiplik modeli bunu dilin kendisine yerleştirir (ayrıntı için Rust Book'un sahiplik bölümüne bakın); Zig böyle bir denetim yapmaz. Standart kütüphanesi ve çalışma zamanı modeli bir servise uyuyorsa Go belgeleri o yaklaşımı iyi anlatır. Bunlardan hiçbiri evrensel olarak daha iyi değildir; doğru seçim projenizin kısıtlarına bağlıdır.

Gerçek projelerden iki örnek: TigerBeetle'ın mimari belgesi Zig'i neden seçtiğini anlatıyor, Ghostty'nin paketleme belgesi ise Zig'in kullanımını ve sabitlenmiş araç zinciri sürümlerini açıklıyor. Bunlar o projelerin kendi tercihleridir; hiçbiri onların Zig 0.17.0 ile derlendiği anlamına gelmez. Zig projesi 26 Kasım 2025'te GitHub'dan Codeberg'e taşındığını duyurdu: Codeberg artık asıl adres, GitHub deposu ise salt okunur. Projeyi finanse eden kuruluş, kâr amacı gütmeyen Zig Software Foundation'dır (ZSF).

Kurulum ve yeni proje

Zig'i resmî indirme sayfasından alın: 0.17.0 sürümünü ve işletim sisteminize uygun arşivi seçin. Zig kurulumu bir yükleyiciden çok açılmış bir klasördür; bu yüzden farklı sürümlerin resmî arşivlerini ayrı klasörlere açıp yan yana tutabilirsiniz. Arşivi açtıktan sonra derleyicinin bulunduğu klasörü PATH'inize ekleyin.

Arşivi çalıştırmadan önce doğrulayın. Bu yazı için Linux x86_64 arşivini geçici bir klasöre indirdim: SHA-256 özeti, resmî index.json dosyasındaki 1cbe9df9f27e6b78d14ccbca43b6703a404ef79ef1c463de901d7f088d4e2026 değeriyle eşleşti; yanındaki Minisign imzası ve güvenilir yorumu da indirme sayfasında yayımlanan genel anahtarla doğrulandı. Açılan derleyici 0.17.0 sürümünü bildirdi. Siz de kendi arşivinizin özetini aynı kaynaktaki değerle karşılaştırın.

Aşağıdaki komutlar Linux x86_64 içindir ve hem arşivin (zig-x86_64-linux-0.17.0.tar.xz) hem de açılmış zig-x86_64-linux-0.17.0 klasörünün bulunduğu dizinde çalıştırılır. Başka bir işletim sistemi ya da mimari kullanıyorsanız arşiv ve klasör adlarını indirdiğiniz dosyayla eşleşecek biçimde değiştirin. export satırı PATH'i yalnızca açık olan kabuk oturumu için değiştirir; kalıcı bir ayar değildir, yeni bir terminalde komutu yeniden vermeniz gerekir.

Linux x86_64 örneği: arşivin SHA-256 özetini hesaplamak, açılmış klasörü geçerli kabuğun PATH'ine eklemek ve sürümü doğrulamak.sh
01sha256sum zig-x86_64-linux-0.17.0.tar.xz02export PATH="$PWD/zig-x86_64-linux-0.17.0:$PATH"03zig version

Sürüm değiştirmeyi sizin yerinize yapan bir araç isterseniz üçüncü taraf zigup var. Ancak README'si bakımcısının başka bir araca geçtiğini söylüyor; bu yüzden onu resmî bir öneri olarak değil, yalnızca bir seçenek olarak anıyorum.

Sürümü doğruladıktan sonra boş bir klasörde projeyi başlatın. zig init şablon projeyi üretir, zig build run onu derleyip çalıştırır, zig build test ise testleri koşturur.

Boş bir klasörde şablon projeyi üretmek, çalıştırmak ve test etmek.sh
01mkdir satir-say02cd satir-say03zig init04zig build run05zig build test

Üretilen projeyi okumak

zig init dört dosya üretir. build.zig, projenin nasıl derleneceğini anlatan Zig kodudur; yani derleme betiği ayrı bir yapılandırma dilinde değil, Zig'in kendisinde yazılır. build.zig.zon paket üst verisini ve bağımlılıkları tutan bildirimdir. src/main.zig çalıştırılabilir dosyanın giriş noktasıdır. src/root.zig ise yeniden kullanılabilir modülün kökü ve testlerin yeridir.

satir-say/: şablonun ürettiği dosya ağacı.text
01satir-say/02├── build.zig03├── build.zig.zon04└── src/05    ├── main.zig06    └── root.zig

Bu rehberde şablonun örnek kodunu kullanmayacağız. build.zig, src/main.zig ve src/root.zig dosyalarını sonraki bölümlerde verilen satir-say kodlarıyla değiştirin; build.zig.zon dosyasını zig init'in ürettiği hâliyle bırakın.

satir-say dosya ağacı: kökte build.zig ve build.zig.zon, src/ altında main.zig ve root.zig; her dosya görevini anlatan numaralı bir kartla eşleşir.
Derleme yapılandırması ve paket üstverisi kökte, CLI girişi ile yeniden kullanılabilir ve testli mantık src/ altında durur.

main.zig komut satırıyla ve G/Ç ile ilgilenir; root.zig ise dosya ya da terminal bilmeyen sayma mantığını ve testlerini tutar, bu yüzden zig build test o dosyadaki test bloklarını çalıştırır.

Dili sayaçla öğrenmek: değerler, hatalar ve temizlik

Önce programın giriş noktasına bakalım. src/main.zig dosyasının tamamı aşağıda. Yazının geri kalanı bu dosyayı ve root.zig'i satır satır dolaşacak; G/Ç satırlarının ayrıntısı 7. bölümde.

src/main.zig: argümanı okur, dosyayı sınırlı okur, sonucu yazar.zig
01const std = @import("std");02const Io = std.Io;03const counter = @import("root.zig");04 05pub fn main(init: std.process.Init) !void {06    const args = try init.minimal.args.toSlice(init.arena.allocator());07    const maybe_path: ?[]const u8 = if (args.len == 2) args[1] else null;08    const path = maybe_path orelse return error.InvalidArguments;09 10    const contents = Io.Dir.cwd().readFileAlloc(11        init.io,12        path,13        init.gpa,14        .limited(16 * 1024 * 1024),15    ) catch |err| {16        std.debug.print("could not read {s}: {s}\n", .{ path, @errorName(err) });17        return err;18    };19    defer init.gpa.free(contents);20 21    const summary = counter.summarize(contents);22 23    var output_buffer: [256]u8 = undefined;24    var output_file_writer: Io.File.Writer = .init(.stdout(), init.io, &output_buffer);25    const output = &output_file_writer.interface;26    try output.print("{s}: {d} lines, {d} bytes\n", .{ path, summary.lines, summary.bytes });27    try output.flush();28}

Zig'de bağlama iki biçimdedir: const bağlanan adın yeniden atanmasını engeller, var ise yeniden atamaya izin verir. const, işaret edilen belleği özyinelemeli olarak değişmez yapmaz; bir dilimin elemanlarına yazılıp yazılamayacağını türdeki const belirler. Örneğin const contents, contents adına başka bir dilim atanamayacağı anlamına gelir; ancak readFileAlloc bir []u8 döndürdüğü için dilimin elemanları değiştirilebilir kalır. Dilim üzerinden elemanlara yazmayı ise []const u8 engeller; path ve summarize'ın data parametresi bu türdedir. Varsayılan tercihiniz const olsun; bu dosyada var yalnızca çıktı tamponunda ve onu saran yazıcıda geçer. Tam sayı türleri bit genişliğiyle yazılır: u8 işaretsiz 8 bit, i32 işaretli 32 bit demektir; tampon [256]u8, yani 256 baytlık bir dizidir. Uzunluk ve indeksler için usize kullanılır; bu, işaretçi boyutundaki işaretsiz tam sayıdır (hedefe göre 32 ya da 64 bit). Türü her zaman yazmanız gerekmez: const summary = counter.summarize(contents) satırında tür sağ taraftan çıkarılır; 16 * 1024 * 1024 gibi sabit bir ifade ise derleme zamanında hesaplanır ve beklenen tam sayı türüne sığıyorsa o türe dönüşür.

Dilim ([]T), bir işaretçi ile bir uzunluktan oluşur ve belleğin sahibi değildir. args[1]'den gelen path, argüman metnine bakan salt okunur bir []const u8 dilimidir; Zig'de dize ayrı bir tür değil, bayt dilimidir. contents farklıdır: readFileAlloc, dosya içeriğini init.gpa ile ayrılmış bir bellekte döndürür ve bu belleği serbest bırakma sorumluluğu artık sizdedir. Argüman dilimlerini ise init.arena üzerinden ayırdık; arena, içindekileri tek tek değil topluca bırakır, bu yüzden onlar için free yazmıyoruz.

?T bir değerin ya var ya da yok olduğunu söyler. maybe_path, ?[]const u8 türündedir: tam iki argüman verildiyse (program adı ve dosya yolu) args[1], aksi hâlde null. orelse, değer null ise sağ tarafı çalıştırır; burada return error.InvalidArguments ile işlevden çıkılır. null kontrolünü atlayamazsınız: derleyici ?T'yi doğrudan T gibi kullanmanıza izin vermez.

!T bir hata birleşimidir: değer ya T'dir ya da bir hata. main'in !void dönüş türü, işlevin başarısız olabileceğini bildirir. try ifade, hata varsa onu çağırana iletir, yoksa değeri verir. catch ise hatayı yerinde ele alır: readFileAlloc(...) catch |err| { ... } bloğu hatanın adını @errorName ile standart hataya yazdırır ve aynı hatayı yeniden döndürür.

defer init.gpa.free(contents) satırı, kapsam hangi yoldan sonlanırsa sonlansın çalışır: normal çıkış, return ya da try'ın ilettiği bir hata. Serbest bırakmayı ayırmanın hemen altına yazmak, onu unutmayı zorlaştırır. errdefer ise yalnızca işlev bir hatayla dönerse çalışır; root.zig'deki kullanımını sonraki bölümde göreceğiz.

Bellek ayırmayı görünür kılmak

Zig'de bellek ayırmayı görünür kılan şey dilin bir kuralı değil, API tasarım geleneğidir: bellek ayıran işlevler çoğunlukla ayırıcıyı çağıranın verdiği bir parametre olarak alır. Derleyici bunu zorunlu kılmaz; ayırıcıyı kendisi seçen bir işlev yazmak da mümkündür. Derleyici sahipliği kanıtlamaz, belleğin serbest bırakıldığını da denetlemez; bunu işlevin sözleşmesi, defer/errdefer ve testler güvence altına alır. Bu rehberdeki newlineOffsets(allocator, data) işlevi geleneği bilerek izler. src/root.zig dosyasının tamamı aşağıda: newlineOffsets, bir std.mem.Allocator kabul eder ve her satır sonunun bayt konumunu ![]usize olarak döndürür. Ayırıcıyı çağıran seçtiği için aynı işlev uygulamada init.gpa ile, testte std.testing.allocator ile çalışabilir.

src/root.zig: sayma mantığı, jenerik yardımcı, ayırıcı kullanan işlev ve beş test.zig
01const std = @import("std");02 03pub const Summary = struct {04    lines: usize,05    bytes: usize,06};07 08pub fn summarize(data: []const u8) Summary {09    var lines: usize = 0;10    for (data) |byte| {11        if (byte == '\n') lines += 1;12    }13    if (data.len > 0 and data[data.len - 1] != '\n') lines += 1;14    return .{ .lines = lines, .bytes = data.len };15}16 17pub fn countMatching(comptime T: type, values: []const T, needle: T) usize {18    var count: usize = 0;19    for (values) |value| {20        if (value == needle) count += 1;21    }22    return count;23}24 25pub fn newlineOffsets(allocator: std.mem.Allocator, data: []const u8) ![]usize {26    var offsets: std.ArrayList(usize) = .empty;27    errdefer offsets.deinit(allocator);28    for (data, 0..) |byte, index| {29        if (byte == '\n') try offsets.append(allocator, index);30    }31    return offsets.toOwnedSlice(allocator);32}33 34test "summarize newline terminated and unterminated input" {35    try std.testing.expectEqual(@as(usize, 2), summarize("a\nb\n").lines);36    try std.testing.expectEqual(@as(usize, 2), summarize("a\nb").lines);37    try std.testing.expectEqual(@as(usize, 0), summarize("").lines);38}39 40test "generic count and compile-time type argument" {41    try std.testing.expectEqual(@as(usize, 2), countMatching(u8, &.{ 1, 2, 1 }, 1));42}43 44test "allocated newline offsets are released by the testing allocator" {45    const offsets = try newlineOffsets(std.testing.allocator, "a\nb\n");46    defer std.testing.allocator.free(offsets);47    try std.testing.expectEqualSlices(usize, &.{ 1, 3 }, offsets);48}49 50test "arena allocator releases a group of allocations" {51    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);52    defer arena.deinit();53    const allocated = try arena.allocator().dupe(u8, "temporary");54    try std.testing.expectEqualStrings("temporary", allocated);55}56 57test "SafeAllocator reports no leaks after explicit free" {58    var safe = std.heap.SafeAllocator.init(std.testing.allocator, .{});59    const allocated = try safe.allocator().dupe(u8, "temporary");60    safe.allocator().free(allocated);61    try std.testing.expectEqual(@as(usize, 0), safe.deinit());62}

var offsets: std.ArrayList(usize) = .empty; boş bir liste başlatır. Bu sürümde ArrayList ayırıcıyı saklamaz; kodda görüldüğü gibi append, deinit ve toOwnedSlice her çağrıda ayırıcıyı ister. Döngü sırasında append bir bellek ayırma hatası verebilir; bu yüzden try kullanıyoruz. O ana kadar biriken bellek için de errdefer offsets.deinit(allocator) yazdık: işlev hatayla dönerse liste serbest bırakılır, başarıyla dönerse bu satır çalışmaz. Son satırda toOwnedSlice, listenin belleğini bir dilime çevirir ve sahipliği çağırana devreder.

İki ayrı akış: newlineOffsets başarıda dilimi çağıranın defer free çağrısına devreder, ayırma hatasında errdefer çalışır ve çağıranın try'ı hatayı yayar; main'de readFileAlloc hatası catch'e gider, tanı yazılır ve hata döndürülür.
errdefer, hata yardımcıdan çıkmadan önce kısmi işi temizler; main'deki catch yalnızca dosya okumayı ele alır ve yardımcının hatalarına bağlı değildir.

Sahiplik dönüş türünde değil, sözleşmede yazılıdır. newlineOffsets döndüğünde dilim çağırana aittir ve çağıran defer allocator.free(...) yazmalıdır; testteki defer std.testing.allocator.free(offsets) satırı tam olarak budur.

Uygulamada ayırıcıyı main'e std.process.Init verir: init.gpa genel amaçlı ayırıcıdır ve satir-say dosyanın bütün içeriğini onunla ayrılan tek bir tamponda tutar. Bir grup geçici ayırmayı birlikte bırakmak istediğinizde ise ArenaAllocator kullanın. Testteki arena.allocator().dupe(u8, "temporary") belleği arenadan alır; defer arena.deinit() bütün grubu tek hamlede serbest bırakır, tek tek free yazmanız gerekmez.

std.testing.allocator testlerde sızıntıları yakalar: bellek ayırıp serbest bırakmadan biten bir test başarısız olur. Bunu alıştırma kartındaki ikinci adımda kendiniz deneyebilirsiniz.

0.17'de sızıntıyı açıkça denetlemek için std.heap.SafeAllocator var. Testimiz onu alttaki ayırıcı olarak std.testing.allocator'ı vererek kuruyor, bir dilim ayırıp serbest bırakıyor ve deinit()'in döndürdüğü sayının 0 olmasını, yani sızıntı bulunmamasını bekliyor. Bu bir çalışma zamanı denetimidir: derleyici sahipliği kanıtlamaz, yalnızca program çalışırken serbest bırakılmayan ayırmalar bulunur. Eski eğitimlerdeki DebugAllocator ve std.heap.Check 0.17'de kullanımdan kaldırıldı; yerlerini SafeAllocator aldı.

Çağıran std.mem.Allocator'ı newlineOffsets'e verir; dönen ![]usize belleğini defer ile çağıran bırakır. Arena geçici ayırmaları arena.deinit() çağrısına kadar gruplar, testler sızıntıyı görünür kılar.
Dilim belleği kimin bırakacağını söylemez: burada dönen belleği çağıran bırakır, arena ise bir grubu tek seferde bırakır.

comptime ve jenerik işlevler

countMatching(comptime T: type, values: []const T, needle: T) işlevi, comptime parametresinin ne işe yaradığını gösterir. T: type bir türdür ve değeri derleme zamanında bilinmek zorundadır. Test countMatching(u8, &.{ 1, 2, 1 }, 1) diye çağırdığında derleyici T = u8 için işlevin ayrı, uzmanlaşmış bir kopyasını üretir; o kopyadaki karşılaştırmalar u8 değerleri içindir.

Burada iki katman var: tür derleme zamanında seçilir, dilimin elemanları ise çalışma zamanında dolaşılıp sayılır. values ve needle çalışma zamanı değerleridir. Yani comptime, “bütün hesabı derlemede yap” demek değildir; yalnızca tür gibi derleme sırasında bilinmesi gereken bir değeri belirtir.

&.{ 1, 2, 1 } ifadesi testte sabit bir dizinin adresidir ve []const u8 dilimine dönüşür; 1 sabitleri de T = u8 olduğu için bayt olur. T parametrenin adıdır; testte ona verilen tür argümanı u8'dir. u8 yerine u32 verseydiniz T = u32 olurdu ve aynı sabitler u32 türünde olurdu: aynı kaynak, farklı türler için çalışır. Zig'de jenerik kod ayrı bir şablon ya da makro dili değildir; derleme zamanında çalışabilen, aynı dilde yazılmış sıradan bir işlevdir. Ayrıntı için dil referansının comptime bölümüne bakın.

Güncel G/Ç: sınırlı okuma, yazıcı arayüzü ve flush

0.16'dan itibaren G/Ç, programa verilen bir std.Io değeri üzerinden yürür. main bu yüzden bir std.process.Init alır; G/Ç yapan çağrılara init.io'yu geçirirsiniz. Dosya okuyan satır şudur: Io.Dir.cwd().readFileAlloc(init.io, path, init.gpa, .limited(16 * 1024 * 1024)). Dosya geçerli çalışma dizinine göre açılır ve içeriği init.gpa'dan ayrılan bir tampona okunur.

.limited(...) okumaya bir üst sınır koyar. satir-say en fazla 16 MiB okur ve dosyanın tamamını belleğe alır; dolayısıyla çok büyük dosyalar için uygun bir araç değildir. Sınırın amacı, beklenmedik büyüklükteki bir girdinin belleği doldurmasını önlemektir.

Çıktı için bir Io.File.Writer kuruyoruz: .init(.stdout(), init.io, &output_buffer) standart çıktıyı, yığında tuttuğumuz 256 baytlık tampona bağlar. Yazıcının genel arayüzü &output_file_writer.interface'tir; biçimlenmiş metni print ile bu arayüze yazarız. Biçim dizesinde {s} bir bayt dizisini metin olarak, {d} bir sayıyı onluk tabanda basar.

Yazıcı tamponludur: print veriyi önce tampona yazar ve çıktının hedefe ulaştığını bu aşamada varsaymamalısınız. Bu yüzden try output.flush() gereklidir; kısa bir çıktıyı flush etmeden bırakırsanız hiçbir şey görmeyebilirsiniz. std.io ve eski dosya sistemi çağrılarıyla yazılmış eğitimler 0.16'dan önceki sürümleri hedefler; onları bu modelle karıştırmayın.

Aracın dış dünyaya verdiği sözleşmeyi açıkça yazalım; çünkü sayma bayt temellidir. Boş girdi sıfır satırdır. Sonunda satır sonu olmayan son satır da sayılır: a\nb iki satırdır. CRLF ile biten dosyalarda yalnızca \n baytları sayıldığı için her satır bir kez sayılır. Çıktıdaki ikinci sayı karakter değil bayttır: ş gibi bir harf UTF-8'de iki bayt tuttuğundan iki olarak sayılır; Unicode karakterlerini saymak ayrı bir iştir ve bu araç onu yapmaz.

Örnek dosyayı oluşturmak ve çalıştırmak; dosyanın son satırında satır sonu yok, yorum satırı beklenen çıktıyı gösteriyor.sh
01printf 'bir\niki\nuc' > sample.txt02zig build run -- sample.txt03# sample.txt: 3 lines, 10 bytes

Bu örnekte sample.txt üç satır ve 10 bayttır: bir, iki ve satır sonu olmayan uc. Argüman verilmezse ya da fazla verilirse program InvalidArguments hatasıyla biter.

Derleme modları, testler ve çapraz derleme

build.zig, zig build komutunun çalıştırdığı bir Zig programıdır. Dosyanın tamamı aşağıda.

build.zig: hedef ve optimizasyon seçenekleri, çalıştırılabilir dosya, run ve test adımları.zig
01const std = @import("std");02 03pub fn build(b: *std.Build) void {04    const target = b.standardTargetOptions(.{});05    const optimize = b.standardOptimizeOption(.{});06 07    const exe = b.addExecutable(.{08        .name = "satir-say",09        .root_module = b.createModule(.{10            .root_source_file = b.path("src/main.zig"),11            .target = target,12            .optimize = optimize,13        }),14    });15    b.installArtifact(exe);16 17    const run_step = b.step("run", "Run the app");18    const run_cmd = b.addRunArtifact(exe);19    run_step.dependOn(&run_cmd.step);20    run_cmd.step.dependOn(b.getInstallStep());21    run_cmd.addPassthruArgs();22 23    const test_module = b.createModule(.{24        .root_source_file = b.path("src/root.zig"),25        .target = target,26    });27    const tests = b.addTest(.{ .root_module = test_module });28    const run_tests = b.addRunArtifact(tests);29    b.step("test", "Run unit tests").dependOn(&run_tests.step);30}

İlk iki satır komut satırı seçeneklerini tanımlar: standardTargetOptions -Dtarget=..., standardOptimizeOption ise -Doptimize=... olarak okunur. addExecutable, src/main.zig kökünden satir-say adlı çalıştırılabilir dosyayı tanımlar; installArtifact onu zig-out/bin/ altına kurar. run adımı kurulum bittikten sonra programı çalıştırır ve addPassthruArgs sayesinde -- sonrasındaki argümanlar satir-say'a iletilir. test adımı da src/root.zig için bir test yürütücüsü derleyip çalıştırır.

Hiçbir seçenek vermezseniz derleme Debug modundadır; geliştirme sırasındaki varsayılan budur ve çalışma zamanı güvenlik denetimleri açıktır. Sürüm modları -Doptimize ile seçilir.

Zig derleme modları
ModAmaçÇalışma zamanı güvenlik denetimleri
DebugGeliştirme için varsayılan modAçık
ReleaseSafeİyileştirmeler açık, denetimler korunurAçık
ReleaseFastHıza öncelik verirKapalı
ReleaseSmallÇıktı boyutuna öncelik verirKapalı

Tablo modların hedefini anlatır, bir ölçüm sonucu değildir; hız ya da boyut farkını kendi programınızda ölçün.

Aşağıdaki komutlar testleri çalıştırır, bir sürüm derlemesi ister ve başka bir hedef seçer.

Testleri çalıştırmak, ReleaseFast derlemek ve Windows hedefi için ayrı bir derleme yapmak.sh
01zig build test02zig build -Doptimize=ReleaseFast03zig build -Dtarget=x86_64-windows-gnu -Doptimize=ReleaseSafe

zig build test, root.zig'deki beş test bloğunu çalıştırır; temiz önbellekle yaptığım çalıştırmada 5/5 test geçti. Testlerin ayırıcıları sızıntıları da yakaladığından bu komut yalnızca mantığı değil, bellek disiplinini de sınar. ReleaseFast derlemesi de başarıyla tamamlandı.

Hedef üçlüsü (x86_64-windows-gnu gibi) -Dtarget ile verilir; -Dtarget=x86_64-windows-gnu -Doptimize=ReleaseSafe derlemesi başarıyla tamamlandı. Burada önemli bir ayrım var: istediğiniz her hedef için ayrı bir zig build çalıştırırsınız; tek bir çağrı bütün hedefleri üretmez. Çapraz derleme bir çıktı üretme işidir: derleyicinin istenen hedef için çıktı ürettiğini gösterir, o çıktının hedef sistemde çalıştığını göstermez. Çıktısını çalıştırıp doğruladığım örnek, ana makinedeki yerel derlemeydi.

Araç zinciri hâlâ gelişiyor. 0.16.0'dan beri x86_64 için derleyicinin kendi arka ucu Debug derlemelerinde varsayılandır; 0.17.0 sürüm notları ise bu arka ucun Windows desteğini tamamlanacak işler arasında sayar. Artımlı derleme -fincremental ile belgelenmiştir ve hedefe ve projeye göre sınırlıdır. İkisini de bir hız garantisi olarak değil, kendi projenizde denenecek gelişen özellikler olarak görün.

C sınırını geçmek ve kontrol listesi

Zig aynı zamanda bir C derleyici sürücüsü olarak kullanılabilir. Mevcut bir C dosyasını Zig araç zinciriyle derlemek için zig cc yeterlidir. Aşağıdaki C11 programını zig cc ile derleyip çalıştırdım; çıktısı C compiled with zig cc oldu.

hello.c: zig cc ile derlenen küçük bir C11 programı.c
01#include <stdio.h>02int main(void) {03    puts("C compiled with zig cc");04    return 0;05}
C dosyasını zig cc ile derlemek ve çalıştırmak.sh
01zig cc -std=c11 -Wall -Wextra hello.c -o hello && ./hello

Bir C başlığını Zig kodundan kullanmak ayrı bir iştir ve burada eski bilgiler tehlikelidir. @cImport 0.16'da kullanımdan kaldırıldı, 0.17'de ise dilden çıkarıldı; standart TranslateC derleme adımı da kullanımdan kaldırılarak yerini ayrı translate-c paketine bıraktı. Resmî sürüm notlarındaki geçiş yolu, bu paketi zig fetch --save ile bağımlılık olarak kaydetmektir. Bu bir bağımlılık getirdiğinden, onu isteğe bağlı ikinci bir mini örnek olarak gösteriyorum.

Örneği satir-say'dan ayrı, zig-c-demo/ adlı bir klasörde kurun. Onun build.zig dosyası farklı bir çalıştırılabilir dosya adı ve bir bağımlılık tanımlar; yani satir-say/build.zig'in yerine geçemez ve iki klasör birbirine karıştırılmamalıdır. Klasörde build.zig.zon yoksa satir-say'da yaptığınız gibi önce zig init çalıştırın, sonra bağımlılığı kaydedin.

zig-c-demo/ içinde translate-c paketini bağımlılık olarak kaydetmek.sh
01zig fetch --save git+https://codeberg.org/ziglang/translate-c

Denediğim zig fetch bağımlılığı 875969d3493e245e01bf5d7860f792d8f3eb9ef5 commit'ine çözümledi ve araç build.zig.zon içine bir içerik özeti yazdı; bağımlılığı sabitleyen bu iki değerdir.

zig-c-demo/src/c.h: Zig'e çevrilecek C başlığı.c
01static inline int zig_demo_value(void) {02    return 42;03}
zig-c-demo/build.zig: translate-c paketinin ürettiği modülü c adıyla içe aktarır.zig
01const std = @import("std");02const Translator = @import("translate_c").Translator;03 04pub fn build(b: *std.Build) void {05    const target = b.standardTargetOptions(.{});06    const optimize = b.standardOptimizeOption(.{});07    const dependency = b.dependency("translate_c", .{});08    const translator: Translator = .init(dependency, .{09        .c_source_file = b.path("src/c.h"),10        .target = target,11        .optimize = optimize,12    });13    const exe = b.addExecutable(.{14        .name = "zig-c-demo",15        .root_module = b.createModule(.{16            .root_source_file = b.path("src/main.zig"),17            .target = target,18            .optimize = optimize,19            .imports = &.{.{ .name = "c", .module = translator.mod }},20        }),21    });22    b.installArtifact(exe);23}
zig-c-demo/src/main.zig: çevrilen C işlevini çağırır.zig
01const std = @import("std");02const c = @import("c");03 04pub fn main() void {05    std.debug.print("C says {d}\n", .{c.zig_demo_value()});06}

build.zig'de b.dependency("translate_c", .{}) bağımlılığı alır, Translator.init başlığı Zig'e çevirir ve translator.mod, ana modüle c adıyla içe aktarılabilir bir modül verir. main.zig de @import("c") ile c.zig_demo_value() işlevini çağırır. Derleyip çalıştırmak C says 42 yazdırdı.

zig-c-demo/ içinde derleyip çalıştırmak; çıktı: C says 42.sh
01zig build && ./zig-out/bin/zig-c-demo

Yazıyı, bu rehberde karşılaştığımız en sık hataları bir araya getiren kısa bir kontrol listesiyle bitirelim:

  • std.io veya 0.16 öncesi dosya sistemi örneklerini sürümünü kontrol etmeden kopyalamayın.
  • async/await'in kararlı, güncel bir dil özelliği olduğunu varsaymayın.
  • @cImport'in 0.17'de hâlâ var olduğunu ya da 0.17 öncesi TranslateC derleme adımını kullanabileceğinizi varsaymayın; translate-c paketini kullanın.
  • Ayrılmış bir dilimi, kimin serbest bırakacağını belgelemeden döndürmeyin; dilim tek başına ayırıcı sahipliğini aktarmaz.
  • defer allocator.free(...) yazmayı, ya da hata dönmeden önce yarım kalan iş için errdefer yazmayı unutmayın.
  • Eski eğitimlerdeki GeneralPurposeAllocator ve std.heap.DebugAllocator'ı 0.17'nin varsayılanı sanmayın; uygulamada init.gpa, açık sızıntı denetimi için SafeAllocator kullanın.
  • Başarılı bir çapraz derlemeyi çalıştırılmış program sanmayın; yalnızca derleyicinin istenen hedef için çıktı ürettiğini gösterir.
  • build.zig.zon'u kararlı bir ekosistem politikası olan bir kayıt defterinin kilit dosyası sanmayın; paket üst verisini ve içerik özetlerini tutar, paket yönetimi ise hâlâ yol haritasında.
  • 0.x API'lerinin kararlı olduğunu ya da editör entegrasyonunun 0.17'nin derleme sistemi protokolünü zaten desteklediğini varsaymayın.

Zig'in çekiciliği, her şeyin açıkta olmasıdır: ayırıcı bir parametredir, hata dönüş türünde yazar, hedef komut satırında seçilir. Bunun karşılığında sürüm ayrıntılarını izlemek size düşer: 0.x API'leri değişebilir, bu yüzden kopyaladığınız her örneği kullandığınız Zig sürümünün belgesiyle karşılaştırın.

Resmî kaynaklar ve ileri okuma

Bu yazıdaki sürüm bilgileri ve API davranışları aşağıdaki kaynaklardan 7 Ekim 2026'da kontrol edildi; kod örnekleri Zig 0.17.0 ile denendi:

  1. Zig: Downloads and public signing key
  2. Zig: download index (index.json)
  3. Zig 0.17.0 release announcement
  4. Zig 0.17.0 release notes
  5. Zig 0.16.0 release announcement
  6. Zig 0.16.0 release notes
  7. Zig 0.17.0 language reference
  8. Zig 0.17.0 standard library reference
  9. Zig: Learn
  10. Zig: Getting started
  11. Zig: Build system guide
  12. Zig: Language overview
  13. Zig: Migrating from GitHub to Codeberg
  14. Zig Software Foundation
  15. Zig repository on Codeberg
  16. translate-c package on Codeberg
  17. Official Zig logo (SVG)
  18. TigerBeetle: architecture
  19. Ghostty: packaging
  20. The Rust Book: Understanding ownership
  21. Go documentation
  22. zigup README (third-party, not an official Zig source)

Sürüme bağlı ayrıntılar Zig 0.17.0'ı izler; 0.x API'leri değişebilir, bu yüzden kendi projenizin kullandığı Zig sürümünün belgelerini esas alın. Zig logosu Zig Software Foundation'a aittir; yalnızca tanıtım amacıyla kullanılmıştır.

✳

Zig'de her şey açıkta olur: ayırıcı, hata ve hedef bilerek seçilir.

Diğer yazılara göz at ↗