نظرة عامة متعمقة

أهم المزايا

لغة بسيطة وسهلة

تفرغ لتصحيح أخطاء برنامجك بدلًا من تصحيح معرفتك بلغة البرمجة.

صياغة Zig الكاملة تحددها وثيقة قواعد لا تتعدى الـ500 سطر.

لا وجود لتحكمات مخفية في التدفّق، أو تخصيصات مخفية للذاكرة، أو معالج تمهيدي، أو وحدات ماكرو. اذا كان الكود ظاهره أنه لا يستدعي وظيفة، فهذا ما يحدث بالفعل. هذا يعني أن بامكانك التأكد من أن الكود التالي يستدعي فقط ()foo ثم ()bar، وهذا مضمون دون أن تحتاج معرفة نوع أيًا من المتغيرات:

var a = b + c.d;
foo();
bar();

أمثلة للتحكم المخفي في التدفّق:

تشجع Zig على جعل صيانة وقراءة الكود سهلة بجعل كل آليات التحكم متاحة حصريًا عن طريق كلمات اللغة المفتاحية والوظائف.

الأداء والأمان: اختر اثنان

لدى Zig أربع أطوار بناء، ويمكن مزج أيًا منهم معًا وصولًا لطبقة النطاق.

المَعلمDebugReleaseSafeReleaseFastReleaseSmall
التحسينات - تحسين للسرعة، ضرر للقدرة على تصحيح الأخطاء، ضرر لوقت الصرفO3-O3-Os-
فحوصات الأمان في زمن التنفيذ - ضرر للسرعة، ضرر للحجم، تحطم بدلًا من سلوك غير محددOnOn

هذا مثال لشكل طفح الأعداد الصحيحة في زمن التصريف، أيًا كان وضع البناء:

test.zig

test "integer overflow at compile time" {
    const x: u8 = 255;
    _ = x + 1;
}
$ zig test test.zig
./doctest-14ad65ca/test.zig:3:11: error: operation caused overflow
    _ = x + 1;
          ^

وهذا شكله في زمن التنفيذ، في الأطوار اللتي تتضمن فحوصات الأمان:

test.zig

test "integer overflow at runtime" {
    var x: u8 = 255;
    x += 1;
}
$ zig test test.zig
Test [1/1] test "integer overflow at runtime"... thread 3336 panic: integer overflow
/home/runner/work/www.ziglang.org/www.ziglang.org/doctest-8fcba8d8/test.zig:3:7: 0x20683f in test "integer overflow at runtime" (test)
    x += 1;
      ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/special/test_runner.zig:77:28: 0x22ccf2 in std.special.main (test)
        } else test_fn.func();
                           ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/start.zig:517:22: 0x22522c in std.start.callMain (test)
            root.main();
                     ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/start.zig:469:12: 0x2092de in std.start.callMainWithArgs (test)
    return @call(.{ .modifier = .always_inline }, callMain, .{});
           ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/start.zig:383:17: 0x208366 in std.start.posixCallMainAndExit (test)
    std.os.exit(@call(.{ .modifier = .always_inline }, callMainWithArgs, .{ argc, argv, envp }));
                ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/start.zig:296:5: 0x208172 in std.start._start (test)
    @call(.{ .modifier = .never_inline }, posixCallMainAndExit, .{});
    ^
error: the following test command crashed:
doctest-8fcba8d8/zig-cache/o/9638dfc29dc05b30774601ed885b4c9f/test /home/runner/work/www.ziglang.org/www.ziglang.org/zig/zig

متفقدات الرص تلك تعمل على كل المعماريات المستهدفة، بما في ذلك المنصات القائمة بذاتها.

مع Zig، يمكن الاعتماد على وضع يتضمن فحوصات الأمان، مع تعطيل هذه الفحوصات بشكل انتقائي في مختنقات الأداء. على سبيل المثال، يمكن تعديل المثال السابق هكذا:

test "actually undefined behavior" {
    @setRuntimeSafety(false);
    var x: u8 = 255;
    x += 1; // XXX undefined behavior!
}

تستعمل Zig السلوك غير المحدد كأداة في غاية الدقة لمنع الأخطاء البرمجية وتحسين الأداء.

وبالحديث عن الأداء، فإن Zig أسرع من C.

برجاء مراعاة أن Zig ليست لغة آمنة بالكامل. لمن يهمه تتبع قصة الأمان في Zig، يمكنكم متابعة هذه الموضوعات:

تتنافس Zig مع C بدل من أن تعتمد عليها

مكتبة Zig الأساسية تستطيع الاندماج مع libc، ولكنها لا تعتمد عليها. هذا مثال مُبسّط:

hello.zig

const std = @import("std");

pub fn main() void {
    std.debug.print("Hello, world!\n", .{});
}
$ zig build-exe hello.zig
$ ./hello
Hello, world!

حين يصرف هذا المثال بوضعية O ReleaseSmall- مع نزع رموز التشخيص، واستعمال وضعية سلسلة التعليمات الواحدة، نحصل على برنامج تنفيذي بحجم 9.8 KiB يستهدف x86_64-linux:

$ zig build-exe hello.zig --release-small --strip --single-threaded
$ wc -c hello
9944 hello
$ ldd hello
  not a dynamic executable

استهداف Windows يصغر حجم البرنامج التنفيذي ليصبح 4096 بايت.

$ zig build-exe hello.zig --release-small --strip --single-threaded -target x86_64-windows
$ wc -c hello.exe
4096 hello.exe
$ file hello.exe
hello.exe: PE32+ executable (console) x86-64, for MS Windows

تعريفات علوية مستقلة

التعريفات العلوية مثل المتغيّرات العمومية مستقلة ولا تعتمد على الترتيب ويتم تحليلها بشكل كسول. القيم التهييئية للمتغيّرات العمومية يتم تقييمها في زمن التصريف.

global_variables.zig

var y: i32 = add(10, x);
const x: i32 = add(12, 34);

test "global variables" {
    assert(x == 46);
    assert(y == 56);
}

fn add(a: i32, b: i32) i32 {
    return a + b;
}

const std = @import("std");
const assert = std.debug.assert;
$ zig test global_variables.zig
Test [1/1] test "global variables"... 
All 1 tests passed.

النوع الاختياري بدلًا من المؤشرات الفارغة

في لغات أخرى، الإشارات الفارغة مصدر للعديد من الاستثناءات في زمن التنفيذ، بل أن البعض يرى أنها اسوأ غلطة في علم الكمبيوتر.

المؤشرات غير المزينة في Zig لا يمكنها أن تكون فارغة:

test "null @intToPtr" {
    _ = @intToPtr(*i32, 0x0);
}
$ zig test test.zig
./doctest-eb2f229b/test.zig:2:9: error: pointer type '*i32' does not allow address zero
    _ = @intToPtr(*i32, 0x0);
        ^

ولكن أي نوع يمكن أن يُحَوّل لنوع اختياري عن طريق استهلاله بـ?:

optional_syntax.zig

const std = @import("std");
const assert = std.debug.assert;

test "null @intToPtr" {
    const ptr = @intToPtr(?*i32, 0x0);
    assert(ptr == null);
}
$ zig test optional_syntax.zig
Test [1/1] test "null @intToPtr"... 
All 1 tests passed.

لفض قيمة اختيارية، يمكن استعمال orelse لنص قيمة افتراضية:

// malloc prototype included for reference
extern fn malloc(size: size_t) ?*u8;

fn doAThing() ?*Foo {
    const ptr = malloc(1234) orelse return null;
    // ...
}

يمكن أيضًا استعمال if:

fn doAThing(optional_foo: ?*Foo) void {
    // do some stuff

    if (optional_foo) |foo| {
        doSomethingWithFoo(foo);
    }

    // do some stuff
}

و نفس الصياغة مع while:

iterator.zig

const std = @import("std");

pub fn main() void {
    const msg = "hello this is dog";
    var it = std.mem.tokenize(u8, msg, " ");
    while (it.next()) |item| {
        std.debug.print("{s}\n", .{item});
    }
}
$ zig build-exe iterator.zig
$ ./iterator
hello
this
is
dog

إدارة ذاكرة يدوية

أي مكتبة مكتوبة بلغة Zig صالحة للاستخدام في أي مكان:

من أجل تحقيق ذلك، فلابد على مبرمجي Zig من إدارة ذاكرتهم بأنفسهم، والتعامل مع فشل تخصيص الذاكرة.

هذا صحيح حتى بالنسبة لمكتبة Zig الأساسية أيضًا. أي وظائف تحتاج لتخصيص الذاكرة تقبل بمُخَصِص كمعَلم. نتيجة هذا هو أن مكتبة Zig الأساسية يمكن استخدامها حتى على المنصات القائمة بذاتها.

بالاضافة لأسلوب جديد للتعامل مع الأخطاء، توفر Zig defer وerrdefer لتبسيط كل عمليات إدارة الموارد، لا الذاكرة فحسب.

لمثال عن defer، تفقد الدمج مع مكتبات C بدون FFI/تقييدات. هذا مثال لاستخدام errdefer:

const Device = struct {
    name: []u8,

    fn create(allocator: *Allocator, id: u32) !Device {
        const device = try allocator.create(Device);
        errdefer allocator.destroy(device);

        device.name = try std.fmt.allocPrint(allocator, "Device(id={d})", id);
        errdefer allocator.free(device.name);

        if (id == 0) return error.ReservedDeviceId;

        return device;
    }
};

أسلوب جديد للتعامل مع الأخطاء

الأخطاء قيم، ولا يمكن إهمالها:

discard.zig

const std = @import("std");

pub fn main() void {
    _ = std.fs.cwd().openFile("does_not_exist/foo.txt", .{});
}
$ zig build-exe discard.zig
./doctest-8708ba50/discard.zig:4:30: error: error is discarded. consider using `try`, `catch`, or `if`
    _ = std.fs.cwd().openFile("does_not_exist/foo.txt", .{});
                             ^

يمكن التعامل مع الأخطاء باستخدام catch:

catch.zig

const std = @import("std");

pub fn main() void {
    const file = std.fs.cwd().openFile("does_not_exist/foo.txt", .{}) catch |err| label: {
        std.debug.print("unable to open file: {e}\n", .{err});
        const stderr = std.io.getStdErr();
        break :label stderr;
    };
    file.writeAll("all your codebase are belong to us\n") catch return;
}
$ zig build-exe catch.zig
$ ./catch
unable to open file: error.FileNotFound
all your codebase are belong to us

الكلمة المفتاحية try هي اختصار لـcatch |err| return err:

try.zig

const std = @import("std");

pub fn main() !void {
    const file = try std.fs.cwd().openFile("does_not_exist/foo.txt", .{});
    defer file.close();
    try file.writeAll("all your codebase are belong to us\n");
}
$ zig build-exe try.zig
$ ./try
error: FileNotFound
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/os.zig:1384:23: 0x224efd in std.os.openatZ (try)
            .NOENT => return error.FileNotFound,
                      ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/fs.zig:999:13: 0x20c67b in std.fs.Dir.openFileZ (try)
            try os.openatZ(self.fd, sub_path, os_flags, 0);
            ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/fs.zig:930:9: 0x20a511 in std.fs.Dir.openFile (try)
        return self.openFileZ(&path_c, flags);
        ^
/home/runner/work/www.ziglang.org/www.ziglang.org/doctest-294604f1/try.zig:4:18: 0x229a55 in main (try)
    const file = try std.fs.cwd().openFile("does_not_exist/foo.txt", .{});
                 ^

لاحظ أن هذا متفقد لتتبع الخطأ وليس متفقد للرص. هذا الكود لم يدفع ثمن حل الرص لكي يعرض هذا المتفقد.

يمكن استخدام كلمة switch المفتاحية مع الخطأ لضمان التعامل مع كل أنواع الأخطاء:

test.zig

const std = @import("std");

test "switch on error" {
    _ = parseInt("hi", 10) catch |err| switch (err) {};
}

fn parseInt(buf: []const u8, radix: u8) !u64 {
    var x: u64 = 0;

    for (buf) |c| {
        const digit = try charToDigit(c);

        if (digit >= radix) {
            return error.DigitExceedsRadix;
        }

        x = try std.math.mul(u64, x, radix);
        x = try std.math.add(u64, x, digit);
    }

    return x;
}

fn charToDigit(c: u8) !u8 {
    const value = switch (c) {
        '0'...'9' => c - '0',
        'A'...'Z' => c - 'A' + 10,
        'a'...'z' => c - 'a' + 10,
        else => return error.InvalidCharacter,
    };

    return value;
}
$ zig test test.zig
./doctest-e1899c0a/test.zig:4:40: error: error.Overflow not handled in switch
    _ = parseInt("hi", 10) catch |err| switch (err) {};
                                       ^
./doctest-e1899c0a/test.zig:3:24: note: referenced here
test "switch on error" {
                       ^
./doctest-e1899c0a/test.zig:4:40: error: error.DigitExceedsRadix not handled in switch
    _ = parseInt("hi", 10) catch |err| switch (err) {};
                                       ^
./doctest-e1899c0a/test.zig:4:40: error: error.InvalidCharacter not handled in switch
    _ = parseInt("hi", 10) catch |err| switch (err) {};
                                       ^

تستخدم كلمة unreachable المفتاحية للتأكيد على عدم إمكانية وقوع أخطاء:

unreachable.zig

const std = @import("std");

pub fn main() void {
    const file = std.fs.cwd().openFile("does_not_exist/foo.txt", .{}) catch unreachable;
    file.writeAll("all your codebase are belong to us\n") catch unreachable;
}
$ zig build-exe unreachable.zig
$ ./unreachable
thread 3427 panic: attempt to unwrap error: FileNotFound
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/os.zig:1384:23: 0x224d4d in std.os.openatZ (unreachable)
            .NOENT => return error.FileNotFound,
                      ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/fs.zig:999:13: 0x20c5eb in std.fs.Dir.openFileZ (unreachable)
            try os.openatZ(self.fd, sub_path, os_flags, 0);
            ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/fs.zig:930:9: 0x20a481 in std.fs.Dir.openFile (unreachable)
        return self.openFileZ(&path_c, flags);
        ^
???:?:?: 0x20bde2 in ??? (???)
/home/runner/work/www.ziglang.org/www.ziglang.org/doctest-adc9fa03/unreachable.zig:4:71: 0x2298e2 in main (unreachable)
    const file = std.fs.cwd().openFile("does_not_exist/foo.txt", .{}) catch unreachable;
                                                                      ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/start.zig:517:22: 0x22210c in std.start.callMain (unreachable)
            root.main();
                     ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/start.zig:469:12: 0x20619e in std.start.callMainWithArgs (unreachable)
    return @call(.{ .modifier = .always_inline }, callMain, .{});
           ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/start.zig:383:17: 0x205226 in std.start.posixCallMainAndExit (unreachable)
    std.os.exit(@call(.{ .modifier = .always_inline }, callMainWithArgs, .{ argc, argv, envp }));
                ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/start.zig:296:5: 0x205032 in std.start._start (unreachable)
    @call(.{ .modifier = .never_inline }, posixCallMainAndExit, .{});
    ^
(process terminated by signal)

هذا يستحضر سلوك غير محدد في أطور البناء غير الآمنة، لذا تأكد من استعماله فقط عندما يكون النجاح مضمونًا.

متفقدات رص لكل المنصات

متفقدات الرص ومتفقدات تتبع الخطأ المعروضة في هذه الصفحة تعمل على كل منصات الفئة الأولى وبعض منصات الفئة الثانية. هذا يشمل أيضًا المنصات القائمة بذاتها!

بالإضافة لذلك، يمكن لمكتبة Zig الأساسية أن تخزن متفقد لرص في أي وقت وتفريغه في وقت لاحق:

stack_traces.zig

const std = @import("std");
const builtin = @import("builtin");

var address_buffer: [8]usize = undefined;

var trace1 = std.builtin.StackTrace{
    .instruction_addresses = address_buffer[0..4],
    .index = 0,
};

var trace2 = std.builtin.StackTrace{
    .instruction_addresses = address_buffer[4..],
    .index = 0,
};

pub fn main() void {
    foo();
    bar();

    std.debug.print("first one:\n", .{});
    std.debug.dumpStackTrace(trace1);
    std.debug.print("\n\nsecond one:\n", .{});
    std.debug.dumpStackTrace(trace2);
}

fn foo() void {
    std.debug.captureStackTrace(null, &trace1);
}

fn bar() void {
    std.debug.captureStackTrace(null, &trace2);
}
$ zig build-exe stack_traces.zig
$ ./stack_traces
first one:
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/debug.zig:186:29: 0x2322cc in std.debug.captureStackTrace (stack_traces)
            addr.* = it.next() orelse {
                            ^
/home/runner/work/www.ziglang.org/www.ziglang.org/doctest-d2d25fcf/stack_traces.zig:27:32: 0x2308ac in foo (stack_traces)
    std.debug.captureStackTrace(null, &trace1);
                               ^
/home/runner/work/www.ziglang.org/www.ziglang.org/doctest-d2d25fcf/stack_traces.zig:17:8: 0x229878 in main (stack_traces)
    foo();
       ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/start.zig:517:22: 0x22212c in std.start.callMain (stack_traces)
            root.main();
                     ^


second one:
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/debug.zig:186:29: 0x2322cc in std.debug.captureStackTrace (stack_traces)
            addr.* = it.next() orelse {
                            ^
/home/runner/work/www.ziglang.org/www.ziglang.org/doctest-d2d25fcf/stack_traces.zig:31:32: 0x2308cc in bar (stack_traces)
    std.debug.captureStackTrace(null, &trace2);
                               ^
/home/runner/work/www.ziglang.org/www.ziglang.org/doctest-d2d25fcf/stack_traces.zig:18:8: 0x22987d in main (stack_traces)
    bar();
       ^
/home/runner/work/www.ziglang.org/www.ziglang.org/zig/lib/std/start.zig:517:22: 0x22212c in std.start.callMain (stack_traces)
            root.main();
                     ^

يمكنك متابعة هذه التقنية في مشروع GeneralPurposeDebugAllocator الجاري.

هياكل بيانات ووظائف عامة

الأنواع قيم يجب معرفتها في زمن التصريف:

types.zig

const std = @import("std");
const assert = std.debug.assert;

test "types are values" {
    const T1 = u8;
    const T2 = bool;
    assert(T1 != T2);

    const x: T2 = true;
    assert(x);
}
$ zig test types.zig
Test [1/1] test "types are values"... 
All 1 tests passed.

هيكل البيانات العام هو مجرد وظيفة ذات عائد نوعه type:

generics.zig

const std = @import("std");

fn List(comptime T: type) type {
    return struct {
        items: []T,
        len: usize,
    };
}

pub fn main() void {
    var buffer: [10]i32 = undefined;
    var list = List(i32){
        .items = &buffer,
        .len = 0,
    };

    std.debug.print("{d}\n", .{list.items.len});
}
$ zig build-exe generics.zig
$ ./generics
10

الاستبطان والتنفيذ في زمن التصريف

وظيفة @typeinfo المضمنة توفر خاصية الاستبطان:

reflection.zig

const std = @import("std");

const Header = struct {
    magic: u32,
    name: []const u8,
};

pub fn main() void {
    printInfoAboutStruct(Header);
}

fn printInfoAboutStruct(comptime T: type) void {
    const info = @typeInfo(T);
    inline for (info.Struct.fields) |field| {
        std.debug.print(
            "{s} has a field called {s} with type {s}\n",
            .{
                @typeName(T),
                field.name,
                @typeName(field.field_type),
            },
        );
    }
}
$ zig build-exe reflection.zig
$ ./reflection
Header has a field called magic with type u32
Header has a field called name with type []const u8

تستخدم مكتبة Zig الأساسية هذه الطريقة في الطباعة المصاغة. على الرغم من كونها لغة بسيطة وسهلة، فالطباعة المصوغة تم تنفيذها بالكامل بلغة Zig. بينما في C، فأخطاء وظائف مثل printf تم غرزها في المترجم. وكذلك في Rust، فوحدة ماكرو الطباعة المصوغة مغروز في المترجم.

تستطيع Zig أيضًا تقييم الوظائف وكتل الكود في وقت التصريف. في بعض الحالات، مثل تهيئة المتغيّرات العمومية، يتم تقييم المصطلح ضمنيًا في وقت الصرف. بخلاف ذلك، يمكن تقييم كود صراحًة في وقت التصريف باستخدام كلمة comptime المفتاحية. هذا يمكنه أن يكون فعالًا اذا تم جمعه مع التأكيدات:

test.zig

const std = @import("std");
const assert = std.debug.assert;

fn fibonacci(x: u32) u32 {
    if (x <= 1) return x;
    return fibonacci(x - 1) + fibonacci(x - 2);
}

test "compile-time evaluation" {
    var array: [fibonacci(6)]i32 = undefined;

    comptime {
        assert(array.len == 12345);
    }
}
$ zig test test.zig
./zig/lib/std/debug.zig:226:14: error: reached unreachable code
    if (!ok) unreachable; // assertion failure
             ^
./doctest-2dbfc381/test.zig:13:15: note: called from here
        assert(array.len == 12345);
              ^
./doctest-2dbfc381/test.zig:9:32: note: called from here
test "compile-time evaluation" {
                               ^
./doctest-2dbfc381/test.zig:13:15: note: referenced here
        assert(array.len == 12345);
              ^

الدمج مع مكتبات C بدون FFI/توصيلات

تستورد @cImport الأنواع والمتغيّرات والوظائف ووحدات الماكرو مباشرة لتصبح جاهزة للاستعمال في Zig. يمكنها أيضًا أن تترجم الوظائف المضمنة من C لـZig.

هذا مثال لإصدار موجة جيبية باستخدام libsoundio:

sine.zig

const c = @cImport(@cInclude("soundio/soundio.h"));
const std = @import("std");

fn sio_err(err: c_int) !void {
    switch (@intToEnum(c.SoundIoError, err)) {
        .None => {},
        .NoMem => return error.NoMem,
        .InitAudioBackend => return error.InitAudioBackend,
        .SystemResources => return error.SystemResources,
        .OpeningDevice => return error.OpeningDevice,
        .NoSuchDevice => return error.NoSuchDevice,
        .Invalid => return error.Invalid,
        .BackendUnavailable => return error.BackendUnavailable,
        .Streaming => return error.Streaming,
        .IncompatibleDevice => return error.IncompatibleDevice,
        .NoSuchClient => return error.NoSuchClient,
        .IncompatibleBackend => return error.IncompatibleBackend,
        .BackendDisconnected => return error.BackendDisconnected,
        .Interrupted => return error.Interrupted,
        .Underflow => return error.Underflow,
        .EncodingString => return error.EncodingString,
        else => return error.Unknown,
    }
}

var seconds_offset: f32 = 0;

fn write_callback(
    maybe_outstream: ?[*]c.SoundIoOutStream,
    frame_count_min: c_int,
    frame_count_max: c_int,
) callconv(.C) void {
    const outstream = @ptrCast(*c.SoundIoOutStream, maybe_outstream);
    const layout = &outstream.layout;
    const float_sample_rate = outstream.sample_rate;
    const seconds_per_frame = 1.0 / @intToFloat(f32, float_sample_rate);
    var frames_left = frame_count_max;

    while (frames_left > 0) {
        var frame_count = frames_left;

        var areas: [*]c.SoundIoChannelArea = undefined;
        sio_err(c.soundio_outstream_begin_write(
            maybe_outstream,
            @ptrCast([*]?[*]c.SoundIoChannelArea, &areas),
            &frame_count,
        )) catch |err| std.debug.panic("write failed: {s}", .{@errorName(err)});

        if (frame_count == 0) break;

        const pitch = 440.0;
        const radians_per_second = pitch * 2.0 * std.math.pi;
        var frame: c_int = 0;
        while (frame < frame_count) : (frame += 1) {
            const sample = std.math.sin((seconds_offset + @intToFloat(f32, frame) *
                seconds_per_frame) * radians_per_second);
            {
                var channel: usize = 0;
                while (channel < @intCast(usize, layout.channel_count)) : (channel += 1) {
                    const channel_ptr = areas[channel].ptr;
                    const sample_ptr = &channel_ptr[@intCast(usize, areas[channel].step * frame)];
                    @ptrCast(*f32, @alignCast(@alignOf(f32), sample_ptr)).* = sample;
                }
            }
        }
        seconds_offset += seconds_per_frame * @intToFloat(f32, frame_count);

        sio_err(c.soundio_outstream_end_write(maybe_outstream)) catch |err| std.debug.panic("end write failed: {s}", .{@errorName(err)});

        frames_left -= frame_count;
    }
}

pub fn main() !void {
    const soundio = c.soundio_create();
    defer c.soundio_destroy(soundio);

    try sio_err(c.soundio_connect(soundio));

    c.soundio_flush_events(soundio);

    const default_output_index = c.soundio_default_output_device_index(soundio);
    if (default_output_index < 0) return error.NoOutputDeviceFound;

    const device = c.soundio_get_output_device(soundio, default_output_index) orelse return error.OutOfMemory;
    defer c.soundio_device_unref(device);

    std.debug.print("Output device: {s}\n", .{device.*.name});

    const outstream = c.soundio_outstream_create(device) orelse return error.OutOfMemory;
    defer c.soundio_outstream_destroy(outstream);

    outstream.*.format = @intToEnum(c.SoundIoFormat, c.SoundIoFormatFloat32NE);
    outstream.*.write_callback = write_callback;

    try sio_err(c.soundio_outstream_open(outstream));

    try sio_err(c.soundio_outstream_start(outstream));

    while (true) c.soundio_wait_events(soundio);
}

$ zig build-exe sine.zig -lsoundio -lc
$ ./sine
Output device: Built-in Audio Analog Stereo
^C

الكود بلغة Zig أبسط بكثير من نظيره بلغة C، بالإضافة للمزيد من فحوصات الأمان، وكل هذا أمكن تحقيقه فقط باستيراد ملف C - بدون توصيلات مع الواجهة البرمجية.

Zig أفضل في استعمال مكتبات C من C نفسها.

Zig أيضًا مترجم للغة C

هذا مثال لـZig وهي تقوم ببناء كود C:

hello.c

#include <stdio.h>

int main(int argc, char **argv) {
    printf("Hello world\n");
    return 0;
}
$ zig build-exe --c-source hello.c --library c
$ ./hello
Hello world

يمكنك استخدام --verbose-cc لمعرفة تفاصيل مترجم لغة الـC الذي تم تنفيذه:

$ zig build-exe --c-source hello.c --library c --verbose-cc
zig cc -MD -MV -MF zig-cache/tmp/42zL6fBH8fSo-hello.o.d -nostdinc -fno-spell-checking -isystem /home/andy/dev/zig/build/lib/zig/include -isystem /home/andy/dev/zig/build/lib/zig/libc/include/x86_64-linux-gnu -isystem /home/andy/dev/zig/build/lib/zig/libc/include/generic-glibc -isystem /home/andy/dev/zig/build/lib/zig/libc/include/x86_64-linux-any -isystem /home/andy/dev/zig/build/lib/zig/libc/include/any-linux-any -march=native -g -fstack-protector-strong --param ssp-buffer-size=4 -fno-omit-frame-pointer -o zig-cache/tmp/42zL6fBH8fSo-hello.o -c hello.c -fPIC

لاحظ أن حين يُنفَذ الأمر مرة أخرى، لا يوجد ناتج، وينتهي فورًا:

$ time zig build-exe --c-source hello.c --library c --verbose-cc

real	0m0.027s
user	0m0.018s
sys	0m0.009s

هذا بفضل التخزين المؤقت لنواتج البناء. تحلل Zig ملف .d وتستعمل نظام تخزين مؤقت متين لتجنب تكرار العمل.

ليس بامكان Zig ترجمة كود C فحسب، بل أن هناك سبب جيد جدًا لاستعمال Zig كمترجم لـC: تُشحن Zig ومعها libc.

صدّر وظائف ومتغيّرات وأنواع جاهزة للاستخدام من كود C

واحدة من الاستخدمات الأساسية لZig هي تصدير مكتبة تستخدم الواجهة الثنائية لC كي تستطيع اللغات الأخرى استخدامها. وضع كلمة export المفتاحية قبل أي وظيفة أو متغيّر أو نوع يضمهم إلى واجهة المكتبة البرمجية:

mathtest.zig

export fn add(a: i32, b: i32) i32 {
    return a + b;
}

لعمل مكتبة ساكنة:

$ zig build-lib mathtest.zig

لعمل مكتبة مشتركة:

$ zig build-lib mathtest.zig -dynamic

هذا مثال لـنظام Zig للبناء:

test.c

#include "mathtest.h"
#include <stdio.h>

int main(int argc, char **argv) {
    int32_t result = add(42, 1337);
    printf("%d\n", result);
    return 0;
}

build.zig

const Builder = @import("std").build.Builder;

pub fn build(b: *Builder) void {
    const lib = b.addSharedLibrary("mathtest", "mathtest.zig", b.version(1, 0, 0));

    const exe = b.addExecutable("test", null);
    exe.addCSourceFile("test.c", &[_][]const u8{"-std=c99"});
    exe.linkLibrary(lib);
    exe.linkSystemLibrary("c");

    b.default_step.dependOn(&exe.step);

    const run_cmd = exe.run();

    const test_step = b.step("test", "Test the program");
    test_step.dependOn(&run_cmd.step);
}

$ zig build test
1379

الترجمة المختلطة استخدام من الفئة الأولى

يمكن لـZig البناء لأي من المنصات في جدول الدعم التي تصنف كفئة ثالثة أو أفضل. لا حاجة لتثبيت أي “سلسلة أدوات مختلطة” أو أي شيء من هذا القبيل. هذا مثال Hello World أصيل:

hello.zig

const std = @import("std");

pub fn main() void {
    std.debug.print("Hello, world!\n", .{});
}
$ zig build-exe hello.zig
$ ./hello
Hello, world!

وهذا مثال لإختيار منصات x86_64-windows وx86_64-macosx وaarch64v8-linux:

$ zig build-exe hello.zig -target x86_64-windows
$ file hello.exe
hello.exe: PE32+ executable (console) x86-64, for MS Windows
$ zig build-exe hello.zig -target x86_64-macosx
$ file hello
hello: Mach-O 64-bit x86_64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
$ zig build-exe hello.zig -target aarch64v8-linux
$ file hello
hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, with debug_info, not stripped

هذا المثال يعمل على أي منصة من الفئة الثالثة+، لأي منصة مستهدفة من الفئة الثالثة+.

تُشحن Zig ومعها libc

يمكن تعديد أهداف libc المتاحة باستخدام zig targets:

...
 "libc": [
  "aarch64_be-linux-gnu",
  "aarch64_be-linux-musl",
  "aarch64_be-windows-gnu",
  "aarch64-linux-gnu",
  "aarch64-linux-musl",
  "aarch64-windows-gnu",
  "armeb-linux-gnueabi",
  "armeb-linux-gnueabihf",
  "armeb-linux-musleabi",
  "armeb-linux-musleabihf",
  "armeb-windows-gnu",
  "arm-linux-gnueabi",
  "arm-linux-gnueabihf",
  "arm-linux-musleabi",
  "arm-linux-musleabihf",
  "arm-windows-gnu",
  "i386-linux-gnu",
  "i386-linux-musl",
  "i386-windows-gnu",
  "mips64el-linux-gnuabi64",
  "mips64el-linux-gnuabin32",
  "mips64el-linux-musl",
  "mips64-linux-gnuabi64",
  "mips64-linux-gnuabin32",
  "mips64-linux-musl",
  "mipsel-linux-gnu",
  "mipsel-linux-musl",
  "mips-linux-gnu",
  "mips-linux-musl",
  "powerpc64le-linux-gnu",
  "powerpc64le-linux-musl",
  "powerpc64-linux-gnu",
  "powerpc64-linux-musl",
  "powerpc-linux-gnu",
  "powerpc-linux-musl",
  "riscv64-linux-gnu",
  "riscv64-linux-musl",
  "s390x-linux-gnu",
  "s390x-linux-musl",
  "sparc-linux-gnu",
  "sparcv9-linux-gnu",
  "wasm32-freestanding-musl",
  "x86_64-linux-gnu",
  "x86_64-linux-gnux32",
  "x86_64-linux-musl",
  "x86_64-windows-gnu"
 ],

وهذا يعني أن library c-- لهذه المنصات لا يعتمد على أي ملفات نظامية!

لنفحص مثال C hello world مرة أخرى:

$ zig build-exe --c-source hello.c --library c
$ ./hello
Hello world
$ ldd ./hello
	linux-vdso.so.1 (0x00007ffd03dc9000)
	libc.so.6 => /lib/libc.so.6 (0x00007fc4b62be000)
	libm.so.6 => /lib/libm.so.6 (0x00007fc4b5f29000)
	libpthread.so.0 => /lib/libpthread.so.0 (0x00007fc4b5d0a000)
	libdl.so.2 => /lib/libdl.so.2 (0x00007fc4b5b06000)
	librt.so.1 => /lib/librt.so.1 (0x00007fc4b58fe000)
	/lib/ld-linux-x86-64.so.2 => /lib64/ld-linux-x86-64.so.2 (0x00007fc4b6672000)

لا تدعم glibc البناء بشكل ساكن، ولكن تدعم musl ذلك:

$ zig build-exe --c-source hello.c --library c -target x86_64-linux-musl
$ ./hello
Hello world
$ ldd hello
  not a dynamic executable

في هذا المثال، بَنت Zig musl libc من كودها المصدري وربطت البرنامج بها. نسخة musl libc المبنية لـx86_64-linux ستبقى متاحة بفضل نظام التخزين المؤقت، لذا فعندما نحتاج لهذه النسخة من libc مجددًا ستكون جاهزة للاستخدام في الحال.

مما يعني أن هذه الخاصية متاحة على كل المنصات. يستطيع مستخدمي Windows وmacOS بناء كود Zig وC، وربط برامجهم بlibc، لأي منصة مذكورة أعلاه. وبالمثل، يمكن ترجمة الكود بشكل مختلط لمعمارية أخرى:

$ zig build-exe --c-source hello.c --library c -target aarch64v8-linux-gnu
$ file hello
hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 2.0.0, with debug_info, not stripped

من بعض النواحي، تعتبر Zig مترجم C أفضل من مترجمي C!

هذه الخاصية أكثر من مجرد شحن سلسلة أدوات للترجمة المختلطة مع Zig. مثلًا، المساحة الكُلّية دون ضغط لملفات libc التعريفية اللتي تُشحن مع Zig هي 22 MiB. بينما الملفات التعريفية الخاصة بmusl libc + Linux على x86_64 8 MiB و مساحة glibc 3.1 MiB (تفتقد glibc لملفات Linux التعريفية)، وZig تُشحن حاليًا ومعها 40 نسخة من libc. لو استخدمنا طريقة ساذجة لأصبح الحجم 444 MiB، ولكن بفضل أداة process_headers وبعض المجهود اليدوي، فإن مساحة ملفات Zig الثنائية المضغوطة تبقى حوالي 30 MiB، على الرغم من دعمها لـlibc لكل هذه المنصات، بالإضافة لcompiler-rt وlibunwind وlibcxx وكونها مترجم للغة C متوافق مع Clang. للمقارنة فملف Clang 8.0.0 الثنائي من llvm.org حجمه 132 MiB.

لاحظ أن المنصات في فئة الدعم الأولى فقط هى التي تم اختبارها بعناية. من المخطط إضافة المزيد من مكتبات libc (ويشمل هذا Windows)، وإضافة اختبارات عند استعمال كل نسخ libc.

هنالك خطة لإضافة مدير حزم Zig، ولكنها لم تكتمل بعد. إحدى الإمكانيات ستكون عمل حزمة لمكتبات C، مما سيجعل نظان بناء Zig جذابًا لمبرجي Zig وC على السواء.

نظام Zig للبناء

تأتي Zig بنظام بناء مدمج، فلا حاجة لmake أو cmake أو أيًا من تلك الأشياء.

$ zig init-exe
Created build.zig
Created src/main.zig

Next, try `zig build --help` or `zig build run`

src/main.zig

const std = @import("std");

pub fn main() anyerror!void {
    std.debug.print("All your base are belong to us.\n");
}

build.zig

const Builder = @import("std").build.Builder;

pub fn build(b: *Builder) void {
    const mode = b.standardReleaseOptions();
    const exe = b.addExecutable("example", "src/main.zig");
    exe.setBuildMode(mode);

    const run_cmd = exe.run();

    const run_step = b.step("run", "Run the app");
    run_step.dependOn(&run_cmd.step);

    b.default_step.dependOn(&exe.step);
    b.installArtifact(exe);
}

لنفحص قائمة help-- لعرض المساعدة.

$ zig build --help
Usage: zig build [steps] [options]

Steps:
  install (default)      Copy build artifacts to prefix path
  uninstall              Remove build artifacts from prefix path
  run                    Run the app

General Options:
  --help                 Print this help and exit
  --verbose              Print commands before executing them
  --prefix [path]        Override default install prefix
  --search-prefix [path] Add a path to look for binaries, libraries, headers

Project-Specific Options:
  -Dtarget=[string]      The CPU architecture, OS, and ABI to build for.
  -Drelease-safe=[bool]  optimizations on and safety on
  -Drelease-fast=[bool]  optimizations on and safety off
  -Drelease-small=[bool] size optimizations on and safety off

Advanced Options:
  --build-file [file]         Override path to build.zig
  --cache-dir [path]          Override path to zig cache directory
  --override-lib-dir [arg]    Override path to Zig lib directory
  --verbose-tokenize          Enable compiler debug output for tokenization
  --verbose-ast               Enable compiler debug output for parsing into an AST
  --verbose-link              Enable compiler debug output for linking
  --verbose-ir                Enable compiler debug output for Zig IR
  --verbose-llvm-ir           Enable compiler debug output for LLVM IR
  --verbose-cimport           Enable compiler debug output for C imports
  --verbose-cc                Enable compiler debug output for C compilation
  --verbose-llvm-cpu-features Enable compiler debug output for LLVM CPU features

لاحظ أن أحد الخيارات الموجودة هو run الذي يقوم بإطلاق البرنامج.

$ zig build run
All your base are belong to us.

هذه بعض الأمثلة لنصوص بناء:

التواقت عن طريق الوظائف غير المتزامنة

Zig 0.5.0 قدمت الوظائف غير المتزامنة. ليست لهذه الميزة أي اعتماديات على نظام التشغيل أو حتى ذاكرة مخصصة من الكومة، مما يعني أن الوظائف غير المتزامنة متاحة للمنصات القائمة بذاتها.

تستنبط Zig إذا ما كانت وظيفة ما غير متزامنة، وتسمح ب async/await مع الوظائف المتزامنة، ممع يعني أن مكتبات Zig لا تعبأ بنظام المدخلات والمخرجات، سواء كان يستعمل الحظر أو عدم التزامن. تتجنب Zig ألوان الوظائف.

تطبق مكتبة Zig الأساسية حلقة أحداث تداول بين الوظائف غير المتزامنة في مَجمع سلاسل لتحقيق تواقت من نوع M:N. تحقيق الأمان ومنع التسابق في حالة تعدد السلاسل موضع أبحاث جارية.

العديد من المنصات المتوفرة للاستهداف

تعتمد Zig نظام “فئات الدعم” لتوضيح مستوى الدعم للمنصات المختلفة.

جدول الدعم المعتمد لنسخة Zig 0.8.0

متعاونون مع القائمون على صيانة الحِزم

مترجم Zig القياسي ليس مستقل بالكامل بعد، ولكن مهما يكن، فسيحتاج البناء بالضبط 3 خطوات. كما ذكرت مايا راشيش، فإن نقل Zig لمنصات أخرى أمر مسل وسريع.

أطوار البناء غير ذات رموز التشخيص حتمية وقابلة للتكرار.

توجد نسخة JSON من صفحة التحميل.

لدى العديد من أعضاء فريق Zig خبرة في صيانة الحِزم.