Zig

Zig是一種通用系統程式語言，採用靜態類型。旨在成為C語言的繼任者、提供更輕量更簡單的編程體驗、提供更豐富的功能，以MIT許可證發布。

Zig和C語言的區別主要在控制流、函數調用、庫導入、變量聲明和Unicode支持，且Zig不支持宏和預處理指令。Zig從現代語言引入了編譯期泛型編程特性，允許函數在編譯期處理各種數據，同時添加了一些新的編譯器指令，以便通過反射訪問和修改類型資訊。

像C語言一樣，Zig沒有實現垃圾回收，而是選擇手動管理記憶體，並添加了提高代碼安全性的功能，例如可選類型和單元測試框架。

Zig最初由Andrew Kelley設計，於2016年發布，開發現由Zig軟體基金會資助。

## 描述

### 目標

Zig 的主要目標是成為解決目前由 C 解決的各種任務的更好的解決方案。對此的主要關注點是可讀性；Zig 儘可能使用現有概念和語法，避免為相似概念添加不同的語法。此外，它的設計目標是「穩健性、最佳化和可維護性」，包括各種提高安全性、優化和測試的功能。簡潔的語法是維護的重要組成部分，因為該語言的目標是允許維護人員在不需要學習他們可能不熟悉的語言細節的情況下調試代碼。即使有這些更改，Zig 也可以編譯並與現有的 C 代碼一起使用；可以在 Zig 項目中包含 C 頭文件並調用它們的函數，並且，通過包含編譯器生成的頭文件，Zig 代碼可以被連結到 C 項目。

為了保持整體設計理念的簡潔和易讀，Zig 系統整體相比於 C 及其他類似 C 的語言也包含了一些風格上的變化。例如，Rust 語言具有 運算符重載，這意味著類似 的語句實際上可能是對類型的重載版本的加運算符的函數調用。此外，該函數可能會引發 panic，從而可能中斷任何後續代碼。在 Zig 中，如果某個東西調用了一個函數，那麼它看起來就像一個函數調用；如果沒有調用，它看起來就不會像一個函數調用。如果它可能拋出錯誤，則一定會在代碼中顯式表示它可能拋出錯誤，而錯誤處理可以通過錯誤類型處理，也可以通過 catch 或 try 處理。

Zig 的目標與同時期設計的許多其他語言（如 Go、Rust、Carbon 和 Nim）的目標形成對比。通常，這些語言更複雜，他們添加了運算符重載、看起來像值（屬性）調用的函數等許多功能，這些旨在幫助構建大型程序。並且這些特性更接近於 C++ 的特性，而這些語言也更像 C++。Zig 在類型系統的擴展上更為保守，支持編譯時泛型，並通過 comptime 特性實現了一種 鴨子類型 形式的用法。

### 記憶體處理

C 程序中錯誤的主要來源之一是基於malloc的記憶體管理系統。malloc 會為代碼使用分配一塊記憶體，並返回該記憶體的引用作為一個 指針，但C沒有一套系統來確保在程序不再需要記憶體時釋放該記憶體，這可能導致記憶體洩漏以至於耗費完所有的可用記憶體。更常見的是懸空指針，這些指針引用未正確分配的記憶體地址。

解決這些問題的常見方法是垃圾回收（GC），它會檢查程序中指向先前分配過記憶體的指針，並刪除所有的不再有指針指向它們的記憶體塊。儘管這在很大程度上減少甚至消除了記憶體錯誤，但GC系統的速度相對較慢，並且具有不可預測的性能，使其不適用於系統編程。另一種解決方案是自動引用計數（ARC），其通過維護指向一個記憶體塊的指針數，並在指針創建和銷毀時更新計數來實現基本相同的功能，這意味著其不需要執行詳盡的指針搜索，而僅增加每個指針在執行創建和銷毀操作時更新引用計數器的開銷。

Zig 旨在提供與 C 相似或更好的性能，因此 GC 和 ARC 不是合適的解決方案。相反，它使用一種現代的（截至 2022 年），被稱為可選類型的概念。與指針可以指向空值或 nil 不同，這種方案使用一個單獨的類型來指示可能為空的數據。這類似於使用一個指針和一個布爾值的結構來指示指針是否有效，但布爾值的狀態由語言隱式管理，不需要程式設計師顯式管理。因此，例如，當聲明指針時，它被設置為「未分配」，而當該指針從 malloc 接收一個值時，如果 malloc 成功，它將被設置為「已分配」。

這種模型的優點是它具有非常低甚至是零的開銷；儘管這使得編譯器必須創建在操作指針時傳遞可選類型，而不是一個簡單的指針的代碼，但這允許它在編譯時直接檢查出可能的記憶體問題，而無需延後到運行時檢查。例如，在 C 中創建一個具有空值的指針並嘗試使用它是完全可以接受的，儘管這會導致空指針錯誤。相比之下，使用可選類型的語言可以保證所有代碼只會在指針有效時嘗試使用相應的指針。雖然這不能消除所有潛在問題，但當在運行時發生問題時，錯誤可以被更精確地定位和解釋。

Zig 中記憶體管理的另一變化是：實際分配操作是通過 struct 描述相應的操作來處理的，而不是直接調用 libc 中的記憶體管理函數。例如，在 C 中，如果想寫一個包含多個副本的字串的函數，該函數可能如下所示：

```c
const char* repeat(const char* original, size_t times);
```

在代碼中，該函數會檢查 original 的大小，然後 malloc times 的記憶體來為它將構建的字串預留空間。但這個 malloc 對調用它的函數是不可見的，所以，如果像 `repeat` 這樣的函數沒有正常釋放記憶體，就會發生記憶體洩漏。而在 Zig 中，這種操作可能通過如下函數處理：

```zig
fn repeat(allocator: *std.mem.Allocator, original: []const u8, times: usize) std.mem.Allocator.Error![]const u8;
```

在此代碼中，allocator 變量傳遞了一個記憶體分配器，並且 repeat 函數則返回結果字串或 Allocator.Error。通過直接將分配器作為輸入傳參，使得記憶體分配不會被「隱藏」起來，即調用鏈上的所有記憶體分配都會通過這個分配接口進行分配。順帶一提，Zig 的標準庫內沒有進行任何隱式記憶體分配。此外，由於結構可以指向任何東西，可以使用替代分配器，甚至是程序中編寫的分配器。這使不使用通常分配整個記憶體頁的作業系統函數的小對象分配器等操作成為了可能。

可選類型是一個提供通用功能但仍然簡單和通用的語言特性。它不僅可以用於解決空指針問題，還可以明確地描述「無值」場景。考慮一個名為 countTheNumberOfUsers 的函數，它返回一個整數，以及一個整數變量，theCountedUsers，它保存結果。在許多語言中，會在 theCountedUsers 中放置一個 魔術數字 以表示 countTheNumberOfUsers 尚未被調用，而許多實現則會將其設置為零。在 Zig 中，這可以實現為 var theCountedUsers: ?i32 = null，將變量設置為明確的「未被調用」的值。

Zig 另一個有助於管理記憶體問題的更通用特性是 defer 的概念，由它標記的代碼在函數結束時無論如何都會被執行，包括可能的運行時錯誤。如果某個特定函數分配了一些記憶體，然後在操作完成時釋放它，可以添加一行代碼，推遲一個 free，以確保無論發生什麼都會釋放它。但需要注意的是，defer 並不完全等價於 RAII，在某些場景下，會有許多區別之處。

Zig 記憶體管理避免了隱式分配。即分配不直接由語言本身進行管理。相反，使用者需要通過 標準庫 明確地進行堆訪問來進行顯式記憶體管理。

### 與 C 的直接交互

Zig 提倡一種將新 Zig 代碼與現有 C 代碼結合的漸進方法。為此，它可以儘可能無縫地與現有 C 庫進行交互。Zig 使用 @import 指令導入庫，通常如下所示：

```zig
const std = @import("std");
```

如此便可以調用 std 內的函數，例如：

```zig
std.debug.print("Hello, world!\n", .{});
```

要加載 C 代碼，只需將 @import 替換為 @cImport：

```zig
const c = @cImport(@cInclude("soundio/soundio.h"));
```

然後我們就可以像調用本地 Zig 代碼一樣調用 soundio 庫中的函數。由於 Zig 使用新數據類型，它們是顯式定義的，不像 C 的更通用的 和 float，我們需要使用少量指令在 C 和 Zig 類型之間移動數據，包括 @intCast 和 @ptrCast。

### 交叉編譯

Zig 將交叉編譯視為語言的一級用例。這意味著任何 Zig 編譯器都可以為其目標平台之一生成可運行的二進位文件，這些平台包括數十種。這些不僅包括廣泛使用的現代系統，如 ARM 和 x86-64，還包括 PowerPC、SPARC、MIPS、RISC-V 甚至 IBM 的 z/Architectures (S390)。工具鏈可以編譯到這些目標中的任何一個，而無需安裝額外的軟體，因為所有需要的支持都在基本系統中。

### 編譯時計算

通過使用 comptime 關鍵字，程式設計師可以顯式地在 編譯時 而不是 運行時 評估代碼段。能夠在編譯時運行代碼使得 Zig 具有 宏 和 條件編譯 的功能，而無需單獨的 預處理器 語言。

在編譯時，類型成為 一級公民。這使得在編譯時 鴨子類型 成為可能，這也是 Zig 實現泛型類型的方式。

例如，在 Zig 中，一個泛型的 鍊表 類型可以使用如下函數實現：

```zig
fn LinkedList(comptime T: type) type;
```

這個函數接收某種類型 T，並返回一個自定義的 struct，定義了包含該數據類型的鍊表。

### 名稱的由來

據報導，「Zig」這個名字是通過一個涉及 Python 腳本的過程選出的，該腳本隨機組合字母，從字母「Z」開始，然後跟隨一個元音或「Y」，以生成四個字母的單詞。儘管目標長度是四個字母，但在生成的各種組合中，最終選擇了三個字母的單詞「Zig」。

## 其他特性

Zig 支持 編譯時 泛型編程、反射編程 和評估、交叉編譯 以及 手動記憶體管理。該語言的一個主要目標是改進 C 語言，同時也從 Rust 等語言中汲取靈感，等等。Zig 具有許多用於 低級程式語言 的特性，特別是緊湊結構（沒有欄位之間的填充的結構）、任意寬度的整數 和多種指針類型。

Zig 不僅僅是一種新語言：它還包括一個 C/C++ 編譯器，可以與這兩種語言一起使用。

## 缺點

Zig 有一些缺點。如果記憶體沒有正確釋放，由於缺乏隱式控制，可能會因為忘記 `defer` 等必要的手動操作導致記憶體洩漏。對於那些不熟悉低級編程概念的人來說，學習 Zig 的曲線可能很陡峭。儘管 Zig 社區在不斷壯大，但截至 2024 年，它仍然是一個新的語言，在成熟度、生態系統和工具方面有待改進。與其他語言的互操作性可能會帶來挑戰，因為這通常需要額外的努力來管理數據編組和通信。最後，複雜用例的學習資源有限，儘管隨著興趣和採用的增加，這種情況正在逐漸改善。

## 版本

自 0.10 版本以來，（新的默認）Zig 編譯器是用 Zig 程式語言編寫的，即它是一個 自舉編譯器，這是該版本的一個重大新特性。舊的遺留 自舉 編譯器是用 C++ 編寫的，仍然是一個選項，但在 0.11 版本中將不再是選項。當使用新的 Zig 編譯器進行編譯時，使用的記憶體要少得多，並且編譯速度稍快。舊的、現在的遺留 C++ 編譯器使用了 3.5 倍的記憶體。

Zig 的默認優化後端仍然是 LLVM，並且 LLVM 是用 C++ 編寫的。帶有 LLVM 的 Zig 編譯器是 169 MiB，而不帶 LLVM 的則是 4.4 MiB。通常情況下，使用新的基於 Zig 語言的編譯器編譯的可執行代碼更快，其 LLVM 代碼生成更好，並修復了許多錯誤，但在 0.10 版本中也對舊的遺留編譯器進行了改進。自託管連結器與自託管編譯器緊密耦合。新版本還增加了一些對 AMD GPU 的實驗性（第 3 層）支持（原始碼中還有對 Nvidia GPU 和 PlayStation 4 和 5 的一些較少支持）。

舊的 自舉 ("stage1") 編譯器是用 Zig 和 C++ 編寫的，使用 LLVM 13 作為後端，支持其許多本機目標。編譯器也是 自由及開放原始碼軟體，在 MIT 許可證 下發布。Zig 編譯器提供了類似於 Clang 的編譯 C 和 C++ 的能力，命令為 `zig cc` 和 `zig c++`，提供了包括 C 標準庫 (libc) 和 C++ 標準庫 (libcxx) 在內的許多不同平台的頭文件，允許 Zig 的 `cc` 和 `c++` 子命令作為開箱即用的 交叉編譯器。

此外，官方支持（並記錄）的作業系統（主要是桌面作業系統）上可以製作（最小）應用程式，這些應用程式也可以為 Android（使用 Android NDK）和 iOS 編程。

## 包管理

在0.11.0版本之前，Zig沒有內置的包管理器，但在0.11.0版本中包含了一個實驗性的包管理器，並在0.12.0中進一步擴展。

Zig沒有官方的包倉庫；相反，包只是一個指向歸檔文件或Git倉庫的URL。理想情況下，當解壓縮時，包括一個標準的`build.zig`文件（Zig 編譯器按慣例使用該文件來編譯原始碼）和一個`build.zig.zon`文件，用於定義包的名稱和版本。

Zig 的開發由 Zig 軟體基金會（ZSF）資助，ZSF 是一個由 Andrew Kelley 擔任總裁的非營利公司，接受捐贈並僱傭多名全職員工。

## 示例

### 泛型鍊表

```zig
const std = @import("std");
const stdout = std.io.getStdOut().writer();

fn LinkedList(comptime T: type) type {
    return struct {
        const Self = @This();
        pub const Node = struct {
            next: ?*Node = null,
            data: T,
        };

        first: ?*Node = null,

        pub fn prepend(
            list: *Self,
            new_node: *Node,
        ) void {
            new_node.next = list.first;
            list.first = new_node;
        }
        pub fn format(
            list: Self,
            comptime fmt: []const u8,
            options: std.fmt.FormatOptions,
            out_stream: anytype,
        ) !void {
            try out_stream.writeAll("( ");
            var it = list.first;
            while (it) |node| : (it = node.next) {
                try std.fmt.formatType(
                    node.data,
                    fmt,
                    options,
                    out_stream,
                    1,
                );
                try out_stream.writeAll(" ");
            }
            try out_stream.writeAll(")");
        }
    };
}

pub fn main() !void {
    const ListU32 = LinkedList(u32);
    var list = ListU32{};
    var node1 = ListU32.Node{ .data = 1 };
    var node2 = ListU32.Node{ .data = 2 };
    var node3 = ListU32.Node{ .data = 3 };
    list.prepend(&node1);
    list.prepend(&node2);
    list.prepend(&node3);
    try stdout.print("{}\n", .{list});
    try stdout.print("{b}\n", .{list});
}
```

- 輸出

```output
( 3 2 1 )
( 11 10 1 )
```

### 帶分配器的字串重複

```zig
const std = @import("std");

fn repeat(
    allocator: *std.mem.Allocator,
    original: []const u8,
    times: usize,
) std.mem.Allocator.Error![]const u8 {
    var buffer = try allocator.alloc(
        u8,
        original.len * times,
    );

    for (0..times) |i| {
        std.mem.copyForwards(
            u8,
            buffer[(original.len * i)..],
            original,
        );
    }

    return buffer;
}

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    var arena = std.heap.ArenaAllocator.init(
        std.heap.page_allocator,
    );
    defer arena.deinit();

    var allocator = arena.allocator();

    const original = "Hello ";
    const repeated = try repeat(
        &allocator,
        original,
        3,
    );

    // 输出 "Hello Hello Hello "
    try stdout.print("{s}\n", .{repeated});
}
```

- 輸出

```output
Hello Hello Hello
```

## 社區

Zig 軟體基金會（ZSF）有一個非常活躍的貢獻者社區，並且仍處於早期發展階段。儘管如此，2024 年的一項 Stack Overflow 調查發現，Zig 軟體開發人員的平均年薪為 103,000 美元，使其成為薪資最高的程式語言之一。然而，只有 0.83% 的受訪者表示他們精通 Zig。

## 項目

- Bun 是一個用 Zig 編寫的 JavaScript 和 TypeScript 運行時，使用 Safari 的 JavaScriptCore 虛擬機。
- Ghostty 是一個用 Zig 編寫的 終端模擬器。
- TigerBeetle 是一個用 Zig 編寫的金融交易資料庫。

## 基本資料

- 編程范型：多範式：命令式、並發、過程式、函數式
- 設計者：Andrew Kelley
- 發行日期：2016年2月8日
- 類型系統：靜態、強、推斷、名義型別、泛型
- 平台：x86-64、ARM64、WebAssembly、Tier 2：ARM、IA-32、RISC-V、MIPS64、POWERPC64、SPARC64、LoongArch
- 作業系統：跨平台：Linux、FreeBSD、Windows
- 許可證：MIT
- 文件擴展名：.zig、.zir、.zon
- 受影響於：C、C++、LLVM IR、Go、Rust