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.
01sha256sum zig-x86_64-linux-0.17.0.tar.xz02export PATH="$PWD/zig-x86_64-linux-0.17.0:$PATH"03zig versionSü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.
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.
01satir-say/02├── build.zig03├── build.zig.zon04└── src/05 ├── main.zig06 └── root.zigBu 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.
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.
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.
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.
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ı.
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.
01printf 'bir\niki\nuc' > sample.txt02zig build run -- sample.txt03# sample.txt: 3 lines, 10 bytesBu ö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.
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.
| Mod | Amaç | Çalışma zamanı güvenlik denetimleri |
|---|---|---|
Debug | Geliştirme için varsayılan mod | Açık |
ReleaseSafe | İyileştirmeler açık, denetimler korunur | Açık |
ReleaseFast | Hıza öncelik verir | Kapalı |
ReleaseSmall | Çıktı boyutuna öncelik verir | Kapalı |
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.
01zig build test02zig build -Doptimize=ReleaseFast03zig build -Dtarget=x86_64-windows-gnu -Doptimize=ReleaseSafezig 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.
zig cc ile derlenen küçük bir C11 programı.c01#include <stdio.h>02int main(void) {03 puts("C compiled with zig cc");04 return 0;05}01zig cc -std=c11 -Wall -Wextra hello.c -o hello && ./helloBir 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.
01zig fetch --save git+https://codeberg.org/ziglang/translate-cDenediğ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.
01static inline int zig_demo_value(void) {02 return 42;03}c adıyla içe aktarır.zig01const 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}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ı.
01zig build && ./zig-out/bin/zig-c-demoYazıyı, bu rehberde karşılaştığımız en sık hataları bir araya getiren kısa bir kontrol listesiyle bitirelim:
std.ioveya 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 öncesiTranslateCderleme adımını kullanabileceğinizi varsaymayın;translate-cpaketini 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çinerrdeferyazmayı unutmayın.- Eski eğitimlerdeki
GeneralPurposeAllocatorvestd.heap.DebugAllocator'ı 0.17'nin varsayılanı sanmayın; uygulamadainit.gpa, açık sızıntı denetimi içinSafeAllocatorkullanı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:
- Zig: Downloads and public signing key
- Zig: download index (index.json)
- Zig 0.17.0 release announcement
- Zig 0.17.0 release notes
- Zig 0.16.0 release announcement
- Zig 0.16.0 release notes
- Zig 0.17.0 language reference
- Zig 0.17.0 standard library reference
- Zig: Learn
- Zig: Getting started
- Zig: Build system guide
- Zig: Language overview
- Zig: Migrating from GitHub to Codeberg
- Zig Software Foundation
- Zig repository on Codeberg
- translate-c package on Codeberg
- Official Zig logo (SVG)
- TigerBeetle: architecture
- Ghostty: packaging
- The Rust Book: Understanding ownership
- Go documentation
- 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.