Go 程序員喜歡 Ziglang?
在編程語言領域,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 > 9) return 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
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/pdDSGEBp7XcT5QyeYYMHFw