在编程语言领域,Zig相对来说是新成员。Zig由Andrew Kelly创立,于2016年正式面世,Zig社区将其定位为“用于开发健壮、优化和可复用软件的通用编程语言”。
在这简单的描述中,蕴含着一些宏大的目标。例如,Zig被视为一个能够与C语言竞争的编程语言(Rust在Linux内核方向进展更快一些)。此外,Zig提供一整套编译工具链,可以替代现有C编译器(Rust可以使用cargo-zigbuild跨平台编译)。
作为一名Go开发者,我对Zig及其工具链的提议尤为感兴趣。在研究Zig时,发现这两种语言(Zig和Go)在某些方面有着共同的特性。在这篇博文中,将重点介绍作为Go程序员,对Zig感兴趣的一些特性。
Zig 与 Go
简洁性
两种语言都秉承简洁的设计哲学,以减少语言的干扰,使开发者更快上手并能够高效地完成开发任务。Zig中没有宏、预处理器或操作符重载等功能,避免了执行流程中的“魔法”。
Go通过运行时处理内存分配和释放。而Zig则坚持其“无隐藏控制流”的准则,没有自动内存管理,而是通过标准库提供内存管理API,让开发者管理内存。
强类型
作为一门系统编程语言,Zig围绕类型系统设计了许多功能,注重安全性和C ABI(应用二进制接口)兼容性。这里简要介绍一些有趣的特性:
- 有符号/无符号整数(预设大小从8位到128位)
- 任意大小的有符号/无符号整数(例如i7表示7位整数)
- 浮点数(精度从16位到128位)
- 切片和数组(例如
[]u8{‘h’, ‘i’, ‘!’}
或[4]i32{1, 2, 3, 4}
) - 以UTF-8编码的字符串字面量,存储为以空字符结尾的字节数组
- 具有丰富功能的结构体类型,可与C ABI兼容
- 具有隐式/显式序数值的枚举,并支持方法
- 可用于存储多种类型值的联合
- 支持使用向量并行操作
- 传统指针及多元素指针与切片表达式
错误处理
Zig中的错误处理机制非常有特色,融合了try-catch异常语义和Go的错误值模式。
首先,所有Zig错误都是必须分配和处理的值(否则会导致编译错误)。错误声明使用error
关键字,如下所示:
1 | const DigitError = error{ TooLarge, }; |
有趣的是,Zig错误值可以与普通类型的值结合,使用!
运算符形成一个联合类型。下面的函数可以返回错误或u32
类型的值:
1 | fn addSingleDigits(a: u32, b: u32) !u32 { |
此外,Zig提供了类似Java等语言的catch
关键字,用于错误处理:
1 | pub fn main() void { |
Zig还支持通过try
关键字将错误向上级调用传播。此外,还可以通过if-else-switch
语句更精确地筛选和处理错误。
Zig测试
在Zig中,测试是语言的关键性能力,使用test
关键字声明:
1 | test "test that 1 + 1 equals 2" { |
使用zig test
命令运行源代码中的测试,在标准库中,测试大多跟源代码处于同一文件。
Zig运行
类似于go run
,Zig提供了zig run
命令,将编译和运行源代码的步骤结合:
1 | zig run my_program.zig |
Defer
Zig与Go一样,通过defer
概念来管理退出堆栈,当作用域块结束时执行清理操作等。
1 | const print = @import("std").debug.print; |
Comptime
comptime
是Zig的一项有趣特性。Zig没有单独的宏系统,而是通过comptime
将其代码编写的灵活性扩展到编译阶段。
comptime
允许在编译时进行如下操作:
- 在编译时解析变量和表达式
- 根据编译时值行为的函数
- 编译期间有选择性地执行
comptime
代码块 - 编译时执行的元编程
泛型
在 Zig 中,comptime
提供了对类型值的访问,可以像普通数据值一样存储和传递这些类型值。
这使得可以创建带有类型参数的函数,如下所示:
1 | fn max(comptime T: type, a: T, b: T) T { |
由于 comptime
类型值被视为普通类型,Zig 允许使用它们来构建泛型数据结构。例如,MakeList
使用 comptime
类型信息来返回一个编译时结构体:
1 | const std = @import("std"); |
在这个示例中,MakeList
函数使用 comptime
类型和大小参数,返回一个包含固定数量元素的结构体。
Zig 编译
- Zig作为 C (交叉) 编译器
Zig 工具链包含完整的 C 编译器,因此可以使用 Zig 来替换当前的 C 编译器工具链。以下是 hello.c
的源代码文件:
1 |
|
使用以下命令,Zig 可以将该源代码编译成可执行的二进制文件:
1 | zig build-exe hello.c --library c |
- Zig 和 C 交叉编译
Zig 让交叉编译(无论是 C 代码还是 Zig 代码亦或Rust)变得简单。无需繁琐的“自行准备交叉编译工具链”。Zig 提供所有必要的工具和库,确保您可以面向其支持的任何架构进行编译。
例如,Zig 可以将上述 C 源代码交叉编译成一个面向 Linux 的静态二进制文件(使用 musl
库):
1 | zig build-exe hello.c --library c -target x86_64-linux-musl |
- Zig 和 CGo 交叉编译
Zig 对 C 的交叉编译对在交叉编译启用了 CGo 的 Go 源代码时特别有用。例如, add.c
中的C 函数 add
:
1 |
|
我们可以在Go中 调用它:
1 | package main |
假设我们在 MacOS 上构建代码,可以使用命令 zig cc
来使用 Zig 的 C 编译器,将 C 代码交叉编译成目标文件并与 Go 的目标文件链接,以构建适用于 x86 架构 Linux 的静态二进制文件:
1 | CGO_ENABLED=1 GOOS=linux CC="zig cc -target x86_64-linux-musl" go build . |
要让这一步成功,您只需安装 Zig 工具链,无其他依赖项!
总结
这篇博客为您简单介绍了 Zig 的功能。Zig融合了简洁性、强大性、安全性和对 C 的兼容性,为开发者提供了一个令人兴奋的选择。无论您是为新项目寻找语言,还是像我一样想扩展编程技能,Zig 都是一个值得探索的创新选择。