Огляд
Основні функції
Маленька, проста мова
Зосередьтеся на налагодженні програми, а не на налагодженні своїх знань мови програмування.
Весь синтаксис Zig указано в 580-рядковому файлі граматики PEG.
Немає прихованого потоку керування, прихованого розподілу пам’яті, препроцесора та макросів. Якщо Zig-код не виглядає так, ніби він відскакує, щоб викликати функцію, це не так. Це означає, що ви можете бути впевнені, що наступний код викликає лише foo()
, а потім bar()
, і це гарантовано без необхідності знати типи чогось:
var a = b + c.d;
foo();
bar();
Приклади прихованого потоку керування:
- У D є функції
@property
, тобто методи, які ви викликаєте за допомогою того, що виглядає як доступ до поля, тому в наведеному вище прикладіc.d
може викликати функцію.. - C++, D і Rust мають перевантаження операторів, тому оператор
+
може викликати функцію. - C++, D і Go мають винятки throw/catch, тому
foo()
може викликати виняток і запобігти викликуbar()
.
Zig сприяє підтримці та читабельності коду, керуючи всіма потоками керування виключно за допомогою ключових слів мови та викликів функцій.
Продуктивність і Безпека: Виберіть Два
Zig має чотири режими збірки, і всі їх можеа комбінувати і узгоджувати аж до scope гранулярності.
Parameter | Debug | ReleaseSafe | ReleaseFast | ReleaseSmall |
---|---|---|---|---|
Оптимізація - покращує швидкість, шкодить налагодженню, шкодить часу компіляції | -O3 | -O3 | -Os | |
Перевірки безпеки під час виконання – швидкість шкоди, розмір шкоди, збій замість невизначеної поведінки | На | На |
Ось як Переповнення Цілого Числа виглядає під час компіляції, незалежно від режиму збирання:
test "integer overflow at compile time" {
const x: u8 = 255;
_ = x + 1;
}
$ zig test 1-integer-overflow.zig
assets/zig-code/features/1-integer-overflow.zig:3:11: error: overflow of integer type 'u8' with value '256'
Ось як це виглядає під час виконання, у збірках з перевірками безпеки:
test "integer overflow at runtime" {
var x: u8 = 255;
x += 1;
}
$ zig test 2-integer-overflow-runtime.zig
1/1 2-integer-overflow-runtime.test.integer overflow at runtime...thread 3030394 panic: integer overflow
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/features/2-integer-overflow-runtime.zig:3:7: 0x103cc2e in test.integer overflow at runtime (test)
x += 1;
^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/compiler/test_runner.zig:157:25: 0x1047f99 in mainTerminal (test)
if (test_fn.func()) |_| {
^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/compiler/test_runner.zig:37:28: 0x103e01b in main (test)
return mainTerminal();
^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:514:22: 0x103d159 in posixCallMainAndExit (test)
root.main();
^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:266:5: 0x103ccc1 in _start (test)
asm volatile (switch (native_arch) {
^
???:?:?: 0x0 in ??? (???)
error: the following test command crashed:
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/.zig-cache/o/9e3bfcb661c1ce3251fa44aa9886f390/test
Ці стек-трейси працюють на всіх цілях, включаючи окремі.
Використовуючи Zig можна покластися на безпечний режим збірки та вибірково вимикати безпеку у вузьких місцях для покращення продуктивності. Наприклад, попередній приклад можна змінити так:
test "actually undefined behavior" {
@setRuntimeSafety(false);
var x: u8 = 255;
x += 1; // XXX undefined behavior!
}
Zig пикористовує Невизначену поведінку як гострий як бритва інструмент як задля запобігання помилкам, так і для підвищення продуктивності.
Говорячи про швидкість роботи, Zig швидше, ніж C.
- Еталонна реалізація використовує LLVM як бекенд для найсучаснішої оптимізації.
- Те, що інші проекти називають «Оптимізацією Консолідації» (Link Time Optimization), Zig робить автоматично.
- Для нативних цілей увімкнено розширені функції ЦП (-march=native), завдяки тому, що Крос-компіляція є першокласним випадком використання.
- Ретельно підібрана невизначена поведінка. Наприклад, у Zig і знакові, і беззнакові цілі мають невизначену поведінку при переповненні, на відміну від лише знакових цілих чисел у C. Це сприяє оптимізації, недоступній у C.
- Zig безпосередньо відкриває тип вектора SIMD, що полегшує написання переносного векторизованого коду.
Зверніть увагу, що Zig не є повністю безпечною мовою. Для тих, хто хоче стежити за історією безпеки Zig, підпишіться на ці випуски:
- перелік всіх видів невизначеної поведінки, навіть тих, які не можна перевірити на безпеку
- зробити режими Debug і ReleaseSafe повністю безпечними
Zig конкурує з C замість того, щоб залежати від нього
Стандартна бібліотека Zig інтегрується з libc, але не залежить від неї. Ось "Hello World":
const std = @import("std");
pub fn main() void {
std.debug.print("Hello, world!\n", .{});
}
$ zig build-exe 4-hello.zig
$ ./4-hello
Hello, world!
При компіляції з -O ReleaseSmall
, символи налагодження видалені, однопоточний режим, це створює статичний виконуваний файл розміром 9,8 КБ для цілі x86_64-linux:
$ zig build-exe hello.zig -O ReleaseSmall -fstrip -fsingle-threaded
$ wc -c hello
9944 hello
$ ldd hello
not a dynamic executable
Збірка Windows ще менша, досягає 4096 байт:
$ zig build-exe hello.zig -O ReleaseSmall -fstrip -fsingle-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
Порядок незалежні декларації верхнього рівня
Оголошення верхнього рівня, такі як глобальні змінні, не залежать від порядку та "ліниво" аналізуються. Значення ініціалізації глобальних змінних оцінюються під час компіляції.
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 5-global-variables.zig
1/1 5-global-variables.test.global variables...OK
All 1 tests passed.
Необовʼязковий тип замість null-вказівників
В інших мовах програмування null-вказівники є джерелом багатьох винятків під час виконання, і їх навіть звинувачують як найгіршу помилку інформатики.
Неприкрашені вказівники Zig не можуть мати значення null:
test "null @intToPtr" {
const foo: *i32 = @ptrFromInt(0x0);
_ = foo;
}
$ zig test 6-null-to-ptr.zig
assets/zig-code/features/6-null-to-ptr.zig:2:35: error: pointer type '*i32' does not allow address zero
Однак будь-який тип можна перетворити на необов’язковий тип, додавши до нього префікс ?:
const std = @import("std");
const assert = std.debug.assert;
test "null @intToPtr" {
const ptr: ?*i32 = @ptrFromInt(0x0);
assert(ptr == null);
}
$ zig test 7-optional-syntax.zig
1/1 7-optional-syntax.test.null @intToPtr...OK
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:
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 10-optional-while.zig
$ ./10-optional-while
hello
this
is
dog
Керування пам'яттю вручну
Бібліотеку, написану мовою Zig, можна використовувати будь-де:
- Настільні програми
- Сервери з низькою затримкою
- Ядро операційної системи
- Вбудовані пристрої
- Програмне забезпечення реального часу, напр. живі виступи, літаки, кардіостимулятори
- У веб-браузерах або інших плагінах із WebAssembly
- Іншими мовами програмування, використовуючи C ABI
Щоб досягти цього, програмісти Zig повинні керувати пам’яттю власноруч та вирішувати проблеми розподілу пам’яті самостійно.
Це також стосується стандартної бібліотеки Zig. Будь-які функції, яким потрібно виділити пам’ять, приймають параметр розподільника. У результаті стандартну бібліотеку 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;
}
};
Новий погляд на обробку помилок
Помилки є значеннями, і їх не можна ігнорувати:
const std = @import("std");
pub fn main() void {
_ = std.fs.cwd().openFile("does_not_exist/foo.txt", .{});
}
$ zig build-exe 12-errors-as-values.zig
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/features/12-errors-as-values.zig:4:30: error: error union is discarded
_ = std.fs.cwd().openFile("does_not_exist/foo.txt", .{});
~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/features/12-errors-as-values.zig:4:30: note: consider using 'try', 'catch', or 'if'
referenced by:
callMain: /home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:514:17
callMainWithArgs: /home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:482:12
remaining reference traces hidden; use '-freference-trace' to see all reference traces
Помилки можна обробляти за допомогою catch:
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: {}\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 13-errors-catch.zig
$ ./13-errors-catch
unable to open file: error.FileNotFound
all your codebase are belong to us
Ключове слово try is a shortcut for catch |err| return err
:
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 14-errors-try.zig
$ ./14-errors-try
error: FileNotFound
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/posix.zig:1768:23: 0x1066a70 in openatZ (14-errors-try)
.NOENT => return error.FileNotFound,
^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/fs/Dir.zig:880:16: 0x1038294 in openFileZ (14-errors-try)
const fd = try posix.openatZ(self.fd, sub_path, os_flags, 0);
^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/fs/Dir.zig:827:5: 0x1034d9e in openFile (14-errors-try)
return self.openFileZ(&path_c, flags);
^
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/features/14-errors-try.zig:4:18: 0x1034be8 in main (14-errors-try)
const file = try std.fs.cwd().openFile("does_not_exist/foo.txt", .{});
^
Зауважте, що це трасування повернення помилки, а не стек-трейс. Код не заплатив ціну розмотування стека, щоб отримати це трасування.
Якщо ключове слово switch використовується до помилки - це забезпечує обробку всіх можливих помилок:
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 build-exe 15-errors-switch.zig
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:509:45: error: root struct of file '15-errors-switch' has no member named 'main'
switch (@typeInfo(@typeInfo(@TypeOf(root.main)).Fn.return_type.?)) {
~~~~^~~~~
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/features/15-errors-switch.zig:1:1: note: struct declared here
const std = @import("std");
^~~~~
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:482:20: note: called from here
return callMain();
~~~~~~~~^~
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:438:36: note: called from here
std.posix.exit(callMainWithArgs(argc, argv, envp));
~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~
referenced by:
_start: /home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:351:40
remaining reference traces hidden; use '-freference-trace' to see all reference traces
Ключове слово unreachable використовується, щоб стверджувати, що помилок не буде:
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 16-unreachable.zig
$ ./16-unreachable
thread 3030318 panic: attempt to unwrap error: FileNotFound
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/posix.zig:1768:23: 0x10699b0 in openatZ (16-unreachable)
.NOENT => return error.FileNotFound,
^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/fs/Dir.zig:880:16: 0x1039a94 in openFileZ (16-unreachable)
const fd = try posix.openatZ(self.fd, sub_path, os_flags, 0);
^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/fs/Dir.zig:827:5: 0x103723e in openFile (16-unreachable)
return self.openFileZ(&path_c, flags);
^
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/features/16-unreachable.zig:4:77: 0x1034fcf in main (16-unreachable)
const file = std.fs.cwd().openFile("does_not_exist/foo.txt", .{}) catch unreachable;
^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:514:22: 0x10347d9 in posixCallMainAndExit (16-unreachable)
root.main();
^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:266:5: 0x1034341 in _start (16-unreachable)
asm volatile (switch (native_arch) {
^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)
Це викликає невизначену поведінку у небезпечних режимах збирання, тому використовуйте його лише тоді, коли успіх гарантований.
Трасування стека на всіх цілях
трасування стека та повернення помилок, показані на цій сторінці, працюють на всіх цілях 1-го рівня і на деяких цілях 2-го рівня підтримки. Навіть окремо!
Крім того, стандартна бібліотека має можливість захопити трасування стека в будь-якій точці, а потім скинути його до стандартної помилки пізніше:
const std = @import("std");
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 17-stack-traces.zig
$ ./17-stack-traces
first one:
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/debug.zig:365:29: 0x10396e7 in captureStackTrace (17-stack-traces)
addr.* = it.next() orelse {
^
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/features/17-stack-traces.zig:26:32: 0x103717c in foo (17-stack-traces)
std.debug.captureStackTrace(null, &trace1);
^
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/features/17-stack-traces.zig:16:8: 0x10350c8 in main (17-stack-traces)
foo();
^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:514:22: 0x1034979 in posixCallMainAndExit (17-stack-traces)
root.main();
^
second one:
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/debug.zig:365:29: 0x10396e7 in captureStackTrace (17-stack-traces)
addr.* = it.next() orelse {
^
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/features/17-stack-traces.zig:30:32: 0x103719c in bar (17-stack-traces)
std.debug.captureStackTrace(null, &trace2);
^
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/features/17-stack-traces.zig:17:8: 0x10350cd in main (17-stack-traces)
bar();
^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:514:22: 0x1034979 in posixCallMainAndExit (17-stack-traces)
root.main();
^
Ви можете побачити, як ця техніка використовується в поточному проекті GeneralPurposeDebugAllocator.
Узагальнені структури даних і функції
Типи - це значення, які повинні бути відомі під час компіляції:
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 18-types.zig
1/1 18-types.test.types are values...OK
All 1 tests passed.
Загальна структура даних — це просто функція, яка повертає type
:
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,
};
list.items[0] = 1234;
list.len += 1;
std.debug.print("{d}\n", .{list.items.len});
}
$ zig build-exe 19-generics.zig
$ ./19-generics
10
Рефлексія під час компіляції та виконання коду під час компіляції
@typeInfo вбудована функція, що забезпечує відображення:
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.type),
},
);
}
}
$ zig build-exe 20-reflection.zig
$ ./20-reflection
20-reflection.Header has a field called magic with type u32
20-reflection.Header has a field called name with type []const u8
Стандартна бібліотека Zig використовує цю техніку для реалізації форматованого друку. Незважаючи на те, що Zig є маленькою, простою мовою, форматований друк Zig повністю реалізований у Zig. Тим часом у C помилки компіляції для printf жорстко закодовані в компіляторі. Так само в Rust форматований макрос друку жорстко закодований у компіляторі.
Zig також може оцінювати функції та блоки коду під час компіляції. У деяких контекстах, таких як ініціалізація глобальної змінної, вираз неявно обчислюється під час компіляції. В іншому випадку можна явно оцінити код під час компіляції за допомогою ключового слова comptime. Це може бути особливо потужним у поєднанні з твердженнями:
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;
@memset(&array, 42);
comptime {
assert(array.len == 12345);
}
}
$ zig test 21-comptime.zig
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/debug.zig:412:14: error: reached unreachable code
assets/zig-code/features/21-comptime.zig:15:15: note: called from here
Інтеграція з бібліотеками 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 (err) {
c.SoundIoErrorNone => {},
c.SoundIoErrorNoMem => return error.NoMem,
c.SoundIoErrorInitAudioBackend => return error.InitAudioBackend,
c.SoundIoErrorSystemResources => return error.SystemResources,
c.SoundIoErrorOpeningDevice => return error.OpeningDevice,
c.SoundIoErrorNoSuchDevice => return error.NoSuchDevice,
c.SoundIoErrorInvalid => return error.Invalid,
c.SoundIoErrorBackendUnavailable => return error.BackendUnavailable,
c.SoundIoErrorStreaming => return error.Streaming,
c.SoundIoErrorIncompatibleDevice => return error.IncompatibleDevice,
c.SoundIoErrorNoSuchClient => return error.NoSuchClient,
c.SoundIoErrorIncompatibleBackend => return error.IncompatibleBackend,
c.SoundIoErrorBackendDisconnected => return error.BackendDisconnected,
c.SoundIoErrorInterrupted => return error.Interrupted,
c.SoundIoErrorUnderflow => return error.Underflow,
c.SoundIoErrorEncodingString => 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 {
_ = frame_count_min;
const outstream: *c.SoundIoOutStream = &maybe_outstream.?[0];
const layout = &outstream.layout;
const float_sample_rate: f32 = @floatFromInt(outstream.sample_rate);
const seconds_per_frame = 1.0 / 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(&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 float_frame: f32 = @floatFromInt(frame);
const sample = std.math.sin((seconds_offset + float_frame *
seconds_per_frame) * radians_per_second);
{
var channel: usize = 0;
while (channel < @as(usize, @intCast(layout.channel_count))) : (channel += 1) {
const channel_ptr = areas[channel].ptr;
const sample_ptr: *f32 = @alignCast(@ptrCast(&channel_ptr[@intCast(areas[channel].step * frame)]));
sample_ptr.* = sample;
}
}
}
const float_frame_count: f32 = @floatFromInt(frame_count);
seconds_offset += seconds_per_frame * float_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 = 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 – немає прив'язок API.
Zig краще використовує бібліотеки C, ніж 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 hello.c --library c
$ ./hello
Hello world
Ви можете використовувати --verbose-cc
, щоб побачити, яку команду компілятора C було виконано:
$ zig build-exe 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 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 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 Build System:
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 може створювати для будь-якої цілі з Таблиці підтримки (see latest release notes) із Підтримкою рівня 3 або кращою. Не потрібно встановлювати "перехресний ланцюжок інструментів" чи щось подібне. Ось рідний Hello World:
const std = @import("std");
pub fn main() void {
std.debug.print("Hello, world!\n", .{});
}
$ zig build-exe 4-hello.zig
$ ./4-hello
Hello, world!
Тепер, щоб створити його для x86_64-windows, x86_64-macos і aarch64-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-macos
$ file hello
hello: Mach-O 64-bit x86_64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
$ zig build-exe hello.zig -target aarch64-linux
$ file hello
hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, with debug_info, not stripped
Це працює для будь-якої цілі Tier 3+.
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 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 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 hello.c --library c -target aarch64-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 МіБ без стиснення. Тим часом заголовки для musl libc + заголовки linux тільки на x86_64 складають 8 МіБ, а для glibc — 3,1 МіБ (у glibc відсутні заголовки linux), але Zig наразі поставляється з 40 бібліотеками libc. З простою комплектацією це було б 444 MiB. Однак завдяки цьому інструменту process_headers і деякій старій добрій ручній праці, двійкові файли архівів Zig залишаються приблизно 30 МіБ, незважаючи на підтримку libc для всіх цих цілей, а також compiler-rt, libunwind і libcxx, і незважаючи на те, що Clang-сумісна компілятор. Для порівняння, сама двійкова збірка Windows clang 8.0.0 від llvm.org становить 132 МіБ.
Зверніть увагу, що лише цілі підтримки рівня 1 були ретельно перевірені. Планується додати більше бібліотек (зокрема для Windows) і [додати тестове покриття для збірки з усіма бібліотеками](https:// github.com/ziglang/zig/issues/2058).
Планується мати Zig Package Manager, але це ще не зроблено. Однією з можливих речей є створення пакета для бібліотек C. Це зробить Zig Build System привабливою як для програмістів Zig, так і для програмістів C.
Система Zig Build
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.
Ось кілька прикладів сценаріїв збірки:
- Збірка сценарію гри OpenGL Tetris
- Створіть сценарій аркадної гри Raspberry Pi 3 на голому металі
- Сценарій збірки самостійного компілятора Zig
Паралелізм через асинхронні функції
Zig 0.5.0 впроваджено асинхронні функції. Ця функція не залежить від операційної системи хоста або навіть від пам’яті, виділеної в купі. Це означає, що асинхронні функції доступні для окремої цілі.
Zig визначає, чи є функція асинхронною, і дозволяє async
/await
для неасинхронних функцій, що означає, що бібліотеки Zig не залежать від блокування та асинхронного введення-виведення. Zig уникає кольорів функцій.
Стандартна бібліотека Zig реалізує цикл подій, який мультиплексує асинхронні функції в пул потоків для паралельності M:N. Багатопотокова безпека та виявлення перегонів є областями активних досліджень.
Підтримується широкий діапазон цілей
Zig використовує систему «рівня підтримки», щоб повідомляти рівень підтримки для різних цілей.
Таблиця підтримки від Zig 0.11.0
Дружнє ставлення до супроводжуючих пакетів
Еталонний компілятор Zig ще не є повністю самостійним, але незважаючи ні на що, залишиться рівно 3 кроки, щоб перейти від системного компілятора C++ мати повністю самостійний компілятор Zig для будь-якої цілі. Як зазначає Майя Рашіш, перенесення Zig на інші платформи — це весело та швидко.
Неналагоджувальні режими збірки є відтворюваними/детермінованими.
Існує JSON-версія сторінки завантаження.
Кілька членів команди Zig мають досвід обслуговування пакетів.
- Daurnimator підтримує Arch Linux пакет
- Marc Tiehuis підтримує пакет Visual Studio Code.
- Andrew Kelley витратив рік чи близько того на Debian and Ubuntu пакети, і випадково сприяє nixpkgs.
- Jeff Fowler підтримує пакет Homebrew і запустив Sublime пакет (зараз підтримується emekoi).