بررسی عمیق

قابلیت های برجسته

زبانی کوچک و ساده

به جای اشکال زدایی دانش زبان برنامه نویسی، بر اشکال زدایی برنامه خود تمرکز کنید.

سینتکس Zig در یک گرامر ۵۰۰ خطی PEG مشخص شده است.

بدون جریان کنترل پنهان، بدون تخصیص حافظه پنهان، بدون پیش پردازنده و بدون ماکرو. اگر کدی در زیگ به نظر نمی رسد که برای فراخوانی یک تابع به سرعت پرش می کند، پس اینطور نیست. این بدان معناست که می توانید مطمئن باشید که کد زیر فقط foo() و سپس bar() را فرا می خواند، و این بدون نیاز به دانستن هر چیزی تضمین می شود:

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

نمونه هایی از کنترل پنهان:

Zig با مدیریت کل جریان کنترل منحصراً با کلمات کلیدی زبان و فراخوانی تابع، نگهداری و خوانایی کد را ارتقا می بخشد.

عملکرد و ایمنی: هر دو را انتخاب کنید

Zig دارای چهار نوع ساخت است، و همه آنها را می توان مخلوط کرده و تا سطح دانه بندی محدوده مطابقت داد.

پارامتراشکال زداییانتشار امنانتشار سریعانتشار کوچک
بهینه سازی ها : افزایش سرعت، صدمه به اشکال زدایی، صدمه به compile time-O3-O3-Os
بررسی ایمنی Runtime : صدمه به سرعت، صدمه به اندازه، کرش به جای رفتار نامشخصOnOn

این چیزی است که عدد صحیح اضافی در Comptime به نظر میرسد، صرف نظر از حالت ساخت:

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;
          ^

Runtime در ساخت های دارای ایمنی:

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

این آثار stack روی همه اهداف کار می کند، شامل freestanding.

با Zig می توان به یک حالت ساخت مجهز به ایمنی اعتماد کرد و به طور انتخابی ایمنی را در تنگناهای عملکرد غیرفعال کرد.مثلا مثال قبلی می تواند به این شکل اصلاح شود:

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

زیگ از رفتار تعریف نشده به عنوان یک ابزار تیغ تیز برای پیشگیری از اشکال و افزایش عملکرد استفاده می کند.

در بحث از عملکرد،Zig سریعتر از C است.

لطفاً توجه داشته باشید که زیگ یک زبان کاملاً امن نیست. برای کسانی که علاقمندند داستان ایمنی زیگ را دنبال کنند، در این ایشو ها مشترک شوید:

Zig به جای وابستگی به C با آن رقابت میکند

کتابخانه استاندارد Zig با libc ادغام می شود، اما به آن وابسته نیست. این نمونه 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!

هنگامی که با -O ReleaseSmall علامت های اشکال زدایی برداشته می شوند و حالت تک رشته ای ایجاد می شود، Zig فایل اجرایی 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

این ساخت در ویندوز حتی کوچک تر است و به 4KB میرسد:

$ 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

ترتیب مستقل اعلام (declaration) های سطح بالا

بیانیه های سطح بالا مانند متغیرهای جهانی مستقل از نظم هستند و با تنبلی تجزیه و تحلیل می شوند. مقادیر مقداردهی اولیه متغیرهای جهانی در Comptime ارزیابی می شوند.

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 defer و errdefer را ارائه میدهد. که برای مدیریت همه منابع ( نه تنها حافظه ) ساده و به راحتی قابل تأیید است.

برای یک مثال defer، ادغام با کتابخانه های C بدون FFI/bindings را ببینید. اینجا یک مثال 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)

رفتار نامشخص را در حالت های ساخت ناامن فرا خوانده میشود، بنابراین مطمئن شوید که فقط در مواقعی که موفقیت تضمین شده است از آن استفاده کنید.

ردگیری استک روی همه اهداف

رد استک نشان داده شده در این صفحه error return traces روی همه ردیف پشتیبانی 1 و برخی از اهداف ردیف پشتیبانی 2 کار میکند. حتی freestanding!

علاوه بر این، کتابخانه استاندارد این قابلیت را دارد که یک رد استک را در هر نقطه ضبط کرده و سپس بعداً آن را به خطای استاندارد منتقل کند.:

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

بازتاب Comptime و اجرای کد Comptile

تابع توکار (builtin) @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/bindings

@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

این کد زیگ به طور قابل توجهی ساده تر از کد C معادل است، و همچنین دارای حفاظت ایمنی بیشتر، و همه اینها با وارد کردن مستقیم فایل هدر C - بدون اتصال API انجام می شود.

Zig در استفاده از کتابخانه های C بهتر از C در استفاده از کتابخانه های C است.

Zig همچنین یک کامپایلر C است

در اینجا نمونه ای از Zig برای ایجاد کد C ذکر شده است:

hello.c

#include <stdio.h>

int main(int argc, char **argv) {
    printf("سلام دنیا\n");
    return 0;
}
$ zig build-exe --c-source hello.c --library c
$ ./hello
سلام دنیا

شما می توانید از --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

این به لطف کش (cache) مصنوعی است. Zig به طور خودکار فایل .d را برای جلوگیری از تکرار کار از یک سیستم کش قوی استفاده می کند.

Zig نه تنها می تواند کد C را کامپایل کند، بلکه دلیل بسیار خوبی برای استفاده از Zig به عنوان کامپایلر C وجود دارد: Zig روی libc سوار میشود.

صادر کردن توابع، متغیر ها و تایپ های C برای وابستگی به آن

یکی از موارد اصلی استفاده از Zig، صادر کردن کتابخانه با C ABI برای سایر زبان های برنامه نویسی است. کلمه کلیدی export در مقابل توابع، متغیرها و انواع باعث می شود که آنها بخشی از API کتابخانه باشند:

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

Cross-compiling در اولویت است

زیگ می تواند برای هر یک از اهداف از جدول پشتیبانی با ردیف پشتیبانی 3 یا بهتر. نیازی به نصب “cross toolchain” یا هر چیزی شبیه آن نیست. این یک 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 بندازیم:

$ 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

در این مثال، musl Zig را از منبع ساخته و سپس در برابر آن را لینک کرده است. ساختار musl libc برای x86_64-linux به لطف caching system موجود است، بنابراین هر زمان که به libc دوباره نیاز باشد فوراً در دسترس خواهد بود.

این بدان معناست که این قابلیت در هر پلتفرمی در دسترس است. کاربران ویندوز و 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 است!

این قابلیت فراتر از یک cross-toolchain به همراه Zig است. به عنوان مثال، اندازه کل هدر های libc که Zig ارسال می کند 22 مگابایت غیر فشرده است. در همین حال، هدر های musl libc + headers در x86_64 به تنهایی 8 MiB و برای glibc 3.1 MiB است (glibc هدر های لینوکس را ندارد)، اما Zig در حال حاضر با 40 libc کار می کند. که با یک ساخت ساده 444 MiB خواهد بود. به هر حال، به لطف process_headers tool که من ساختم، و چند کار دستی خوب قدیمی، مجموع سایز باینری به 30 MiB میرسد. با وجود پشتیبانی از libc برای همه این اهداف و همچنین compiler-rt، libunwind و libcxx، با وجود اینکه یک کامپایلر C سازگار با clang است. ساختار باینری clang 8.0 ویندوز در خود سایت llvm.org به 130 MB میرسد.

توجه داشته باشید که فقط اهداف ردیف پشتیبانی 1 به طور کامل آزمایش شده است. برنامه ریزی شده است اضافه کردن libcs بیشتر (از جمله برای Windows)، و افزودن پوشش آزمایش برای ساخت در برابر همه libcs

توجه داشته باشید که فقط اهداف هدف ردیف 1 به طور کامل آزمایش شده اند. اضافه کردن libcs بیشتر برنامه ریزی شده است (از جمله برای Windows). افزودن پوشش آزمایش برای ساخت در برابر همه libcs

برنامه ریزی شده است که یک Zig Package Manager داشته باشید، اما هنوز انجام نشده است. یکی از مواردی که امکان پذیر است ایجاد بسته برای کتابخانه های C است. این امر Zig Build System را برای برنامه نویسان Zig و برنامه نویسان C جذاب می کند.

سیستم ساخت Zig

Zig دارای یک سیستم ساخت است، پس به make، cmake و هر چیزی مشابه آن نیازی ندارید.

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

then, 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

می بینید که یکی از مراحل موجود اجرا شده است.

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

در اینجا چند نمونه از اسکریپت های ساخت وجود دارد:

همزمانی از طریق توابع Async

Zig 0.5.0 معرفی توابع async. این ویژگی به سیستم عامل میزبان یا حتی حافظه اختصاص داده شده وابسته نیست. این بدان معناست که توابع async برای هدف مستقل در دسترس هستند.

Zig استنباط می کند که آیا یک تابع همگام است یا نه، و async/await را در توابع غیر همگام امکان پذیر می کند، به این معنی که کتابخانه های Zig از مسدود کردن در مقابل async I/O جلوگیری می کنند. Zig avoids function colors.

کتابخانه استاندارد Zig یک حلقه رویداد را اجرا می کند که عملکردهای همزمان را در یک مخزن thread برای همزمان M: N چند برابر می کند. ایمنی چند رشته ای و تشخیص مسابقه حوزه های تحقیقاتی فعال هستند.

طیف گسترده ای از اهداف پشتیبانی می شود

زیگ از یک سیستم “ردیف پشتیبانی” برای ارتباط سطح پشتیبانی از اهداف مختلف استفاده می کند.

[جدول پشتیبانی Zig 0.8.0]

با نگهدارندگان بسته دوستانه رفتار می کند

مرجع کامپایلر Zig هنوز کاملا خود میزبان نیست، اما فرقی ندارد چه باشد، فقط 3 مرحله برای داشتن یک کامپایلر C++ و داشتن یک کامپایلر Zig خود میزبان برای هر هدفی وجود دارد. همانطور که Maya Rashish گفت، انتقال Zig به سایر سیستم عامل ها سرگرم کننده و سریع است.

اشکال زدایی حالت های ساخت قابل تکرار/قطعی هستند.

اینجا یک نسخه JSON از صفحه دانلود وجود دارد.

چندین نفر از اعضای تیم زیگ تجربه نگهداری بسته ها را دارند.