Go程序员喜欢Ziglang?

文摘   科技   2024-11-07 07:01   江苏  
在编程语言领域,Zig相对来说是新成员。Zig由Andrew Kelly创立,于2016年正式面世,Zig社区将其定位为“用于开发健壮、优化和可复用软件的通用编程语言”。作为一名Go开发者,我对Zig及其工具链的能力尤为感兴趣。在研究Zig时,发现这两种语言(Zig和Go)在某些方面有着共同的特性。


译|zouyee


为了帮助读者深入了解Kubernetes在各种应用场景下所面临的挑战和解决方案,以及如何进行性能优化。我们推出了<<Kubernetes经典案例30篇>>

在编程语言领域,Zig相对来说是新成员。Zig由Andrew Kelly创立,于2016年正式面世,Zig社区将其定位为“用于开发健壮、优化和可复用软件的通用编程语言”。

在这简单的描述中,可以发现其蕴含着一些宏大的目标。例如,Zig被视为一个能够与C语言竞争的编程语言(Rust在Linux内核方向进展更快一些)。此外,Zig提供一整套编译工具链,可以替代现有C编译器(Rust可以使用cargo-zigbuild跨平台编译)。

作为一名Go开发者,我对Zig及其工具链的提议尤为感兴趣。在研究Zig时,发现这两种语言(Zig和Go)在某些方面有着共同的特性。在这篇博文中,将重点介绍作为Go程序员,对Zig感兴趣的一些特性。




Ziglang与Go

简洁性

两种语言都秉承简洁的设计哲学,以减少语言的干扰,使开发者更快上手并能够高效地完成开发任务。Zig中没有宏、预处理器或操作符重载等功能,避免了执行流程中的“魔法”。

Go通过运行时处理内存分配和释放。而Zig则坚持其“无隐藏控制流”的准则,没有自动内存管理,而是通过标准库提供内存管理API,让开发者管理内存。


强类型

作为一门系统编程语言,Zig围绕类型系统设计了许多功能,注重安全性和C ABI(应用二进制接口)兼容性。这里简要介绍一些有趣的特性:

a. 有符号/无符号整数(预设大小从8位到128位)

b. 任意大小的有符号/无符号整数(例如i7表示7位整数)

c. 浮点数(精度从16位到128位)

d. 切片和数组(例如 `[]u8{‘h’, ‘i’, ‘!’}` 或 `[4]i32{1, 2, 3, 4}`)

e. 以UTF-8编码的字符串字面量,存储为以空字符结尾的字节数组

f. 具有丰富功能的结构体类型,可与C ABI兼容

g. 具有隐式/显式序数值的枚举,并支持方法

h. 可用于存储多种类型值的联合

i. 支持使用向量并行操作

j. 传统指针及多元素指针与切片表达式


错误处理

Zig中的错误处理机制非常有特色,融合了try-catch异常语义和Go的错误值模式。

首先,所有Zig错误都是必须分配和处理的值(否则会导致编译错误)。错误声明使用`error`关键字,如下所示:

const DigitError = error{ TooLarge, };


有趣的是,Zig错误值可以与普通类型的值结合,使用`!`运算符形成一个联合类型。下面的函数可以返回错误或`u32`类型的值:

fn addSingleDigits(a: u32, b: u32) !u32 {
if (a > 9) return error.TooLarge;
if (b > 9) return error.TooLarge;
return a + b;
}


此外,Zig提供了类似Java等语言的`catch`关键字,用于错误处理:

pub fn main() void {
const result = addSingleDigits(4, 5) catch |err| {
std.debug.print("Error: {}\n", .{err});
return;
};
std.debug.print("Result: {}\n", .{result});
}

Zig还支持通过`try`关键字将错误向上级调用传播。此外,还可以通过`if-else-switch`语句更精确地筛选和处理错误。


Zig测试

在Zig中,测试是语言的关键性能力,使用`test`关键字声明:

test "test that 1 + 1 equals 2" {
const result = 1 + 1;
assert(result == 2);
}

使用`zig test`命令运行源代码中的测试,在标准库中,测试大多跟源代码处于同一文件。


Zig运行

类似于`go run`,Zig提供了`zig run`命令,将编译和运行源代码的步骤结合:

zig run my_program.zig


Defer

Zig与Go一样,通过`defer`概念来管理退出堆栈,当作用域块结束时执行清理操作等。

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

fn addSingleDigits(a: u32, b: u32) !u32 {
defer print("this is deferred!");    
if (a > 9) return error.TooLarge;
    if (b > 9return error.TooLarge;
return a + b;
}


Comptime

comptime是Zig的一项有趣特性。Zig没有单独的宏系统,而是通过comptime将其代码编写的灵活性扩展到编译阶段。

comptime允许在编译时进行如下操作:

a. 在编译时解析变量和表达式

b. 根据编译时值行为的函数

c. 编译期间有选择性地执行`comptime`代码块

d. 编译时执行的元编程


泛型

在 Zig 中,comptime提供了对类型值的访问,可以像普通数据值一样存储和传递这些类型值。

这使得可以创建带有类型参数的函数,如下所示:

fn max(comptime T: type, a: T, b: T) T {
return if (a > b) a else b;
}


test "max with different types" {
const condition = false;
const result = max(if (condition) f32 else u64, 1234, 5678);
_ = result;
}


由于 comptime类型值被视为普通类型,Zig 允许使用它们来构建泛型数据结构。例如,MakeList使用 comptime 类型信息来返回一个编译时结构体:

const std = @import("std");

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


pub fn main() void {
var list = MakeList(i32, 3){
.items = [3]i32{1, 2, 3},
};
std.debug.print("List {}\n", .{list});
}

在这个示例中,`MakeList` 函数使用 `comptime` 类型和大小参数,返回一个包含固定数量元素的结构体。


Zig 编译

1. Zig作为 C (交叉) 编译器

Zig 工具链包含完整的 C 编译器,因此可以使用 Zig 来替换当前的 C 编译器工具链。以下是 `hello.c` 的源代码文件:

#include <stdio.h>

int main(int argc, char **argv) {
printf("Hello world\n");
return 0;
}


使用以下命令,Zig 可以将该源代码编译成可执行的二进制文件:

zig build-exe hello.c --library c


2. Zig 和 C 交叉编译

Zig 让交叉编译(无论是 C 代码还是 Zig 代码亦或Rust)变得简单。无需繁琐的“自行准备交叉编译工具链”。Zig 提供所有必要的工具和库,确保您可以面向其支持的任何架构进行编译。

例如,Zig 可以将上述 C 源代码交叉编译成一个面向 Linux 的静态二进制文件(使用 `musl` 库):

zig build-exe hello.c --library c -target x86_64-linux-musl


3. Zig 和 CGo 交叉编译

Zig 对 C 的交叉编译对在交叉编译启用了 CGo 的 Go 源代码时特别有用。例如, `add.c` 中的C 函数 `add`:

#include <stdint.h>
int32_t add(int32_t a, int32_t b) {
return a + b;
}

我们可以在Go中 调用它:

package main/*
#include "add.c"
*/
import "C"
import (
"fmt"
)


func main() {
a, b := int32(3), int32(4)
result := C.add(a, b)
fmt.Printf("%d + %d = %d\n", a, b, result)
}


假设我们在 MacOS 上构建代码,可以使用命令 `zig cc` 来使用 Zig 的 C 编译器,将 C 代码交叉编译成目标文件并与 Go 的目标文件链接,以构建适用于 x86 架构 Linux 的静态二进制文件:

CGO_ENABLED=1 GOOS=linux CC="zig cc -target x86_64-linux-musl" go build .


要让这一步成功,您只需安装 Zig 工具链,无其他依赖项!




Zig的突出特点


Zig致力于成为一个更好的C语言替代品,其不仅适用于低级系统编程,还适用于开发通用软件系统,具有以下突出特点:

设计简单 

现代化语言的设计目标是提供一套设计良好的语法,而不像汇编语言那样原子化。如果语言的抽象过于接近汇编语言,开发人员可能需要编写冗长的代码。另一方面,当语言被抽象成接近人类可读时,它可能与硬件相距甚远,可能不适合系统编程的需求。

Zig提供了轻量级的、类Rust的语法,其大多数C提供的能力都已具备,但是它不提供Rust和C++那些复杂的功能集和语法,而是提供了一个像Go那样简单性为先的开发路径。

性能和安全性 

性能和安全性是选择的关键因素。语言的性能通常取决于其标准库、核心运行时功能的性能,以及编译器生成的二进制文件的质量。同时,安全设计实现边界检查、溢出处理和内存范围,并帮助开发人员减少关键安全漏洞。

Zig构建系统提供了四种构建模式,开发人员可以根据其性能和安全性要求使用。Zig还可以在编译时理解变量溢出。

此外,它可以生成带有运行时安全检查的优化二进制文件,就像Rust一样,也可以生成不带运行时安全检查的超轻量级二进制文件,就像C一样。Zig官方文档声称,由于其基于LLVM的优化和改进的未定义行为,Zig在理论上比C更快!

完整的系统编程解决方案

大多数编程语言都有一个或多个标准编译器和标准库实现。例如,您可以使用以下编译C:

- GNU C 

- Apple Clang 

- 带有libc、BSD-libc和Microsoft C运行时的MSVC编译器 

但是这两个组件对于现代系统编程需求来说还不够。程序员通常需要建立工具、包管理器和交叉编译工具等。

因此,在C生态系统中,像CMake、Ninja、Meson这样的构建工具以及类似Conan这样的包管理器逐渐流行,而像Go和Rust这样的现代语言官方内置了包管理器、构建工具及API、交叉编译支持和测试集成等。

与Go及Rust等现代语言一样,Zig内置了包管理器、构建系统及API、支持交叉编译和测试集成,这提高了Zig成为更好的C的机会,因为它解决了C(和C++)开发人员面临的关键系统编程问题。从语言设计的角度来看,Zig提供了C开发人员期望的现代语言的所有功能,因此C程序员可以逐步将他们的系统迁移到现代Zig,而无需重新编写他们遗留的代码库



总结


本文,为您简单介绍了 Zig 的功能。Zig融合了简洁性、强大性、安全性和对 C 的兼容性,为开发者提供了一个令人兴奋的选择。无论您是为新项目寻找语言,还是像我一样想扩展编程技能,Zig 都是一个值得探索的创新选择。

Zig仍然是一种新语言,ZSF仍在定期实现和测试更多功能。学习Zig是一个很好的决定,因为它作为一种更好的C语言,有着光明的前景。




由于笔者时间、视野、认知有限,本文难免出现错误、疏漏等问题,期待各位读者朋友、业界专家指正交流。

参考文献
1.https://ziglang.org/documentation/master/.
2.https://medium.com/@vladimirvivien/things-i-like-about-zig-as-a-go-programmer-75eb02aab00f





真诚推荐你关注



Kubernetes经典案例30篇

ziglang30分钟速成

Rust vs. Zig:究竟谁更胜一筹?性能、安全性等全面对决!




来个“分享、点赞、在看”👇

DCOS
CNCF 云原生基金会大使,CoreDNS 开源项目维护者。主要分享云原生技术、云原生架构、容器、函数计算等方面的内容,包括但不限于 Kubernetes,Containerd、CoreDNS、Service Mesh,Istio等等