← 返回
学习
深入了解
功能特色 小巧而简单的语言 专注于调试你的应用程序,而不是调试你的编程语言知识。
Zig 的完整语法可以被 500 行的 PEG 语法文件 所描述。
没有隐式控制流 ,没有隐式内存分配,没有预处理器,也没有宏。如果 Zig 代码看起来不像是在调用一个函数,那么它就不是。这意味着你可以确定下面的代码只会先调用 foo()
,然后调用 bar()
,不需要知道任何元素的类型,这一点也是可以保证的:
var a = b + c . d ;
foo ( ) ;
bar();
隐式控制流的例子:
D 有 @property
函数,可以让你的方法调用看起来像是成员访问,因此在上面的例子中,c.d
可能会调用一个函数。 C++、D 和 Rust 有运算符重载,因此 +
可能会调用一个函数。 C++、D 和 Go 可以抛出和捕获异常,因此 foo()
可能会抛出一个异常,并且将会阻止 bar()
被调用。 Zig 将所有的控制流完全用语言关键字和函数调用来表达,以此促进代码的维护性和可读性。
性能和安全:全都要 Zig 有 4 种构建模式 ,它们可以从全局到代码作用域的粒度 下被任意混合以匹配需求。
以下是编译期整数溢出 的例子,无关编译模式选择:
1-integer-overflow.zig test "integer overflow at compile time" {
const x: u8 = 255 ;
_ = x + 1 ;
}
Shell $ 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'
这是在启用了安全检查的构建中运行时的场景:
2-integer-overflow-runtime.zig test "integer overflow at runtime" {
var x: u8 = 255 ;
x += 1 ;
}
Shell $ 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
这些堆栈跟踪在所有目标上都可用 ,包括裸金属(freestanding) 。
有了 Zig,人们可以依赖启用安全检查的构建模式,并在性能瓶颈处选择性地禁用安全检查。例如前面的例子可以这样修改:
3-undefined-behavior.zig test "actually undefined behavior" {
@setRuntimeSafety (false );
var x: u8 = 255 ;
x += 1 ;
}
Zig 将未定义行为 作为一个利器,既可以预防 bug,又可以提升性能。
说到性能,Zig 比 C 快。
参考实现使用 LLVM 作为后端进行最先进的优化。 其他项目所谓的“链接时优化”,在 Zig 是自动达成的。 多亏了对交叉编译的一流支持 ,对于原生构建目标,高级 CPU 特性可以被启用(相当于 -march=native
)。 精心选择的未定义行为。例如,在 Zig 中,有符号和无符号整数在溢出时都属于未定义的行为,而在 C 中仅有有符号整数的溢出属于未定义行为,这有助于实现 C 语言里没有的优化 。 Zig 直接暴露了 SIMD 向量类型 ,使得编写跨平台的向量化代码更容易。 请注意,Zig 不是一个完全安全的语言。有兴趣关注 Zig 安全故事的用户,可以订阅下面这些链接:
Zig 与 C 竞争,而不是依赖于它 Zig 标准库里集成了 libc,但是不依赖于它。以下是 Hello World 示例:
4-hello.zig const std = @import ("std" );
pub fn main () void {
std.debug.print("Hello, world!\n" , .{});
}
Shell $ zig build-exe 4-hello.zig
$ ./4-hello
Hello, world!
当使用 -O ReleaseSmall
并移除调试符号,单线程模式构建,可以产生一个以 x86_64-linux 为目标的 9.8 KiB 的静态可执行文件:
$ 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
顺序无关的顶层声明 全局变量等顶层声明与顺序无关,并进行惰性分析。全局变量的初始值在编译时进行求值 。
5-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;
Shell $ zig test 5-global-variables.zig
1/1 5-global-variables.test.global variables...OK
All 1 tests passed.
用可选类型代替空指针 在其他编程语言中,空引用是许多运行时异常的来源,甚至被指责为计算机科学中最严重的错误 。
不加修饰的 Zig 指针不可为空:
6-null-to-ptr.zig test "null @intToPtr" {
const foo: *i32 = @ptrFromInt (0x0 );
_ = foo;
}
Shell $ 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
当然,任何类型都可以通过在前面加上 ?
来变成一个可选类型 :
7-optional-syntax.zig const std = @import ("std" );
const assert = std.debug.assert;
test "null @intToPtr" {
const ptr: ?*i32 = @ptrFromInt (0x0 );
assert(ptr == null );
}
Shell $ zig test 7-optional-syntax.zig
1/1 7-optional-syntax.test.null @intToPtr...OK
All 1 tests passed.
要解开一个可选的值,可以使用 orelse
来提供一个默认值:
8-optional-orelse.zig
extern fn malloc (size: size_t) ?*u8 ;
fn doAThing () ?*Foo {
const ptr = malloc(1234 ) orelse return null ;
}
另一种选择是使用 if:
9-optional-if.zig fn doAThing (optional_foo: ?*Foo) void {
if (optional_foo) |foo| {
doSomethingWithFoo(foo);
}
}
相同的语法也可以在 while 中使用:
10-optional-while.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});
}
}
Shell $ zig build-exe 10-optional-while.zig
$ ./10-optional-while
hello
this
is
dog
手动内存管理 用 Zig 编写的库可以在任何地方使用:
为了达到这个目的,Zig 程序员必须管理自己的内存,必须处理内存分配失败。
Zig 标准库也是如此。任何需要分配内存的函数都会接受一个分配器参数。因此,Zig 标准库甚至可以用于裸金属(freestanding)的目标。
除了对错误处理的全新诠释 ,Zig 还提供了 defer 和 errdefer ,使所有的资源管理——不仅仅是内存——变得简单且易于验证。
关于 defer
的例子,请看无需 FFI/bindings 的 C 库集成 。下面是一个使用 errdefer
的例子: 11-errdefer.zig 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;
}
};
错误处理的全新诠释 错误是值,不可忽略:
12-errors-as-values.zig const std = @import ("std" );
pub fn main () void {
_ = std.fs.cwd().openFile("does_not_exist/foo.txt" , .{});
}
Shell $ 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 所处理:
13-errors-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: {}\n" , .{err});
const stderr = std.io.getStdErr();
break :label stderr;
};
file.writeAll("all your codebase are belong to us\n" ) catch return ;
}
Shell $ zig build-exe 13-errors-catch.zig
$ ./13-errors-catch
unable to open file: error.FileNotFound
all your codebase are belong to us
关键词 try 是 catch |err| return err
的简写:
14-errors-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" );
}
Shell $ 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 关键词可以用于确保所有可能的错误都被处理:
15-errors-switch.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;
}
Shell $ 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 用于断言不会发生错误:
16-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 ;
}
Shell $ 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)
这将会在不安全构建中出现未定义行为 ,因此请确保只在一定会成功的地方使用。
在所有目标上启用堆栈跟踪 本页所展示的堆栈跟踪和错误返回跟踪 适用于所有一级支持 和部分二级支持 目标,甚至裸金属(freestanding)目标 !
此外,标准库能在任何一点捕获堆栈跟踪,然后将其转储为标准错误:
17-stack-traces.zig 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);
}
Shell $ 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 项目中看到这种技术的应用。
泛型数据结构和函数 类型和值必须在编译期已知:
18-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);
}
Shell $ zig test 18-types.zig
1/1 18-types.test.types are values...OK
All 1 tests passed.
泛型数据结构简单来说就是一个函数返回一个类型:
19-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 ,
};
list.items[0 ] = 1234 ;
list.len += 1 ;
std.debug.print("{d}\n" , .{list.items.len});
}
Shell $ zig build-exe 19-generics.zig
$ ./19-generics
10
编译期反射和编译期代码执行 @typeInfo 内置函数可以用于提供编译期反射:
20-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.type ),
},
);
}
}
Shell $ 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 中实现的。同时,在 C 语言中,printf
的编译错误是硬编码到编译器中的。同样,在 Rust 中,格式化打印的宏也是硬编码到编译器中的。
Zig 还可以在编译期对函数和代码块求值。在某些情况下,比如全局变量初始化,表达式会在编译期隐式地进行求值。除此之外我们还可以使用 comptime 关键字显式地在编译期求值。把它与断言相结合就可以变得尤为强大了:
21-comptime.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 ;
@memset (&array, 42 );
comptime {
assert(array.len == 12345 );
}
}
Shell $ 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
无需 FFI/bindings 的 C 库集成 @cImport 可以为 Zig 直接导入类型、变量、函数和简单的宏。它甚至能将 C 内联函数翻译成 Zig 函数。
这是一个利用 libsoundio 库发出正弦波的例子:
sine.zig 22-sine-wave.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 库。
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 23-math-test.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 24-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。
4-hello.zig const std = @import ("std" );
pub fn main () void {
std.debug.print("Hello, world!\n" , .{});
}
Shell $ 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
在任意三级支持以上的目标平台,都可以构建任何三级支持以上的目标。
Zig 与 libc 一起发布 你可以通过 zig targets
命令获得可用的 libc 目标:
...
"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",
"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-linux-gnu",
"x86-linux-musl",
"x86-windows-gnu",
"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 的缓存仍然有效,所以当再次需要这个 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 捆绑在一起。例如,Zig 提供的 libc 头文件未压缩时总大小为 22 MiB。同时,仅 x86_64 上的 musl libc 和 Linux 头文件就有 8 MiB,glibc 有 3.1 MiB(glibc 缺少 Linux 头文件),而 Zig 目前提供了 40 个 libc。如果采用简单的捆绑方式,Zig 的体积将达到 444 MiB。尽管 Zig 支持所有这些目标的 libc,以及 compiler-rt、libunwind 和 libcxx,而且尽管它还是一个 Clang 兼容的 C 编译器,但多亏了 process_headers 工具,以及一些体力劳动 ,Zig 二进制压缩包的总容量仍然只有大约 30 MiB。相比之下,llvm.org 提供的的 clang 8.0.0 本身的 Windows 二进制包就有 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 25-all-bases.zig const std = @import ("std" );
pub fn main () anyerror !void {
std.debug.print("All your base are belong to us.\n" );
}
build.zig 26-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
你可以看到,其中一个可用的步骤(step)被运行。
$ zig build run
All your base are belong to us.
以下是一些构建脚本的例子:
使用异步函数进行并发 Zig 0.5.0 引入了异步函数(英文) 。该功能不依赖于宿主操作系统,甚至不依赖于堆分配的内存。这意味着异步函数可以用于裸金属(freestanding)目标。
Zig 自动推导函数是否为异步,并允许在非异步函数上进行 async
/await
,这意味着 Zig 库对阻塞与异步 I/O 是不可知的 。Zig 避免了函数染色(英文) 。
Zig 标准库实现了一个事件循环,将异步函数复用到线程池上,实现 M:N 并发。多线程安全和竞争检测是尚在积极研究的领域。
支持广泛的目标 Zig 使用“支持等级”系统来描述不同目标的支持程度。
截至 Zig 0.11.0 的支持表格
对包维护者友好 虽然 Zig 编译器还没有完全自托管,但无论如何,从拥有一个系统 C++ 编译器到拥有一个适用于任何目标的完全自托管的 Zig 编译器,将保持正好 3 步 。正如 Maya Rashish 所指出的那样,将 Zig 移植到其他平台是有趣且快速的 。
非调试模式 的构建是可重现/确定的。
这是JSON格式的下载页面 。
Zig 团队的几位成员都有维护软件包的经验。