Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 8 additions & 13 deletions course/advanced/atomic.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,16 @@ outline: deep

在讲述下列的内建函数前,我们需要了解一下前置知识:

**原子操作的顺序级别**:为了实现性能和必要保证之间的平衡,原子性分为六个级别。它们按照强度顺序排列,每个级别都包含上一个级别的所有保证。
**原子操作的顺序级别(Memory Ordering)**:为了实现性能和必要保证之间的平衡,原子操作提供了不同的内存排序级别。它们按照约束强度从弱到强排列:

关于原子顺序六个级别的具体说明,见 [LLVM](https://llvm.org/docs/Atomics.html#atomic-orderings)。
- **Unordered**:最弱的原子保证。仅保证操作本身是原子的(不会被撕裂),但不提供任何跨线程的顺序保证。
- **Monotonic**(对应 C++ 的 `memory_order_relaxed`):保证同一线程内对同一变量的原子操作是单调有序的,但不阻止不同变量之间的操作重排序。适用于简单的计数器等场景。
- **Acquire**:读操作使用。保证当前线程在此读操作**之后**的所有读写操作,不会被重排到此操作之前。常用于获取锁。
- **Release**:写操作使用。保证当前线程在此写操作**之前**的所有读写操作,不会被重排到此操作之后。常用于释放锁。
- **AcqRel**(Acquire + Release):同时具备 Acquire 和 Release 语义,适用于读 - 改-写(Read-Modify-Write)操作。
- **SeqCst**(Sequentially Consistent):最强的保证。除了包含 AcqRel 的所有保证外,还保证所有线程观察到的 SeqCst 操作的顺序是一致的。开销最大,但最易于推理。

<!-- **NotAtomic**

简单的非原子加载或者存储,即常规加载或存储。

**Unordered**

无序的原子级别,是最低级别。意味着一组操作可以以任意的顺序原子执行,只需要结果而不管过程以何种顺序执行。

**Monotonic**

保证原子操作是单调的,在一个线程中,所观察到的原子值在后续过程中必定是大于或等于当前值。但在多线程中,并不保证不会发生重排序 -->
关于更多细节,见 [LLVM Atomics](https://llvm.org/docs/Atomics.html#atomic-orderings)。

### [`@atomicLoad`](https://ziglang.org/documentation/master/#atomicLoad)

Expand Down
12 changes: 7 additions & 5 deletions course/advanced/comptime.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ outline: deep

在开始之前,我们需要先梳理一下,什么是**编译期**

对于这个概念,你可能会一脸懵逼。所以我们先看一下什么是运行时(runtime)
要理解编译期,我们先回顾一下**运行时(Runtime)**的概念

> 在计算机科学中代表一个计算机程序从开始执行到终止执行的运作、执行的时期。”
> 在计算机科学中,运行时代表一个计算机程序从开始执行到终止执行的运作、执行的时期。”
对应地,我们可以尝试给编译期做一个定义:“zig 编译期是指在 zig 编译期间执行的动作。”
对应地,**编译期求值(Compile-time Evaluation)** 是指在编译阶段——即源代码被翻译为机器码的过程中——对表达式进行求值的机制。与运行时求值不同,编译期求值的结果会被直接嵌入到最终的二进制产物中,不产生任何运行时开销。

:::info 🅿️ 提示

Expand All @@ -25,9 +25,11 @@ outline: deep
- 在这个调用点,标记的值必须是在编译期已知的,否则 zig 会报告错误!
- 在函数定义中,该值(包括参数、类型)必须是编译期已知的(但无需全部都是编译期已知的,仅保证依赖关系中的符合即可)!

## 编译期参数实现鸭子类型
## 编译期参数实现泛型(参数多态)

> “当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
> "当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。"
上面这句话描述的是动态语言中的"鸭子类型"。Zig 的编译期泛型与之类似但有本质区别:Zig 是在**编译期**通过参数多态(Parametric Polymorphism)来实现泛型的——类型作为编译期参数传入,编译器会为每个具体类型生成特化的代码,所有类型检查都在编译期完成。

一个实现 `max` 功能的函数:

Expand Down
2 changes: 1 addition & 1 deletion course/advanced/interact-with-c.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ pub const MAKELOCAL =

应尽量避免使用此类型,通常它仅出现在翻译输出代码中。

导入 C 头文件后,zig 并不知道如何处理指针(因为 C 的指针可以同时作为单项指针和多项指针使用),这会导致歧义,故 zig 引入一种新类型 `[*c]T`,作为一种折中方案,新类型 `[*c]T` 具有以下特点:
导入 C 头文件后,Zig 并不知道如何处理指针(因为 C 的指针可以同时作为单项指针和多项指针使用),这会导致歧义。为此,Zig 引入了 `[*c]T` 类型作为**兼容性退化机制**——它在类型信息不完整时保持与 C 的互操作性,同时允许开发者后续将其转换为更精确的 Zig 指针类型。`[*c]T` 具有以下特点:

1. 支持 zig 普通指针(`*T` 和 `[*]T`)的全部语法。
2. 可以强制转换为其他的任意指针类型,当然也包括可选指针类型(当被转换为非可选指针时,如果地址为 0,此时会触发安全检查的保护机制,报错并通知出现了未定义行为)。
Expand Down
2 changes: 1 addition & 1 deletion course/advanced/memory_manage.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ outline: deep
6. [`page_allocator`](https://ziglang.org/documentation/master/std/#std.heap.page_allocator)
7. [`StackFallbackAllocator`](https://ziglang.org/documentation/master/std/#std.heap.StackFallbackAllocator)

除了这八种内存分配模型外,还提供了内存池的功能 [`MemoryPool`](https://ziglang.org/documentation/master/std/#std.heap.memory_pool.MemoryPool)
除了这七种内存分配模型外,还提供了内存池的功能 [`MemoryPool`](https://ziglang.org/documentation/master/std/#std.heap.memory_pool.MemoryPool)

你可能对上面的多种内存模型感到很迷惑,C 语言中不就是 `malloc` 吗,怎么到这里这么多的“模型”,这些模型均有着不同的特点,而且它们之间有一部分还可以叠加使用,Zig 在这方面提供了更多的选择,而且不仅仅是这些,你还可以自己尝试实现一个内存模型。

Expand Down
6 changes: 4 additions & 2 deletions course/advanced/reflection.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ outline: deep

# 反射

> 在计算机学中,反射(**reflection**,是指计算机程序在运行时(**runtime**)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为
> 在计算机科学中,反射(**Reflection**是指程序在执行过程中可以访问、检测和修改自身结构或行为的一种能力
事实上,由于 zig 是一门强类型的静态语言,因此它的反射是在编译期实现的,允许我们观察已有的类型,并根据已有类型的信息来创造新的类型!
反射通常包含两个层面:**内省(Introspection)**——即程序检查自身类型信息的能力;以及**中间表示操纵(Intercession)**——即程序修改自身结构的能力。

在传统的动态语言(如 Python、Ruby)中,反射发生在运行时。而 Zig 是一门强类型的静态语言,它的反射完全在**编译期**实现——我们可以在编译期观察已有类型的信息(内省),并根据这些信息构建全新的类型(有限度的中间表示操纵)。这种编译期反射的优势在于:不会引入任何运行时开销,且所有类型错误都能在编译期被捕获。

## 观察已有类型

Expand Down
2 changes: 1 addition & 1 deletion course/advanced/type_cast.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ undefined 是一个神奇的值,它可以赋值给所有类型,代表这个

## 对等类型转换

对等类型转换(**Peer Type Resolution**,这个词汇仅仅在 zig 的文档中出现过,它看起来与前面提到的普通类型解析很像,根据 zig[开发手册](https://ziglang.org/documentation/master/)所述,它发生在以下情况:
对等类型转换(**Peer Type Resolution**是 Zig 类型系统中的一种类型推断机制。其核心思想是:当多个表达式需要产生统一类型的结果时,编译器会自动寻找它们的**最小公共超类型(Least Upper Bound)**——即能够无损表示所有参与类型的最小类型。根据 Zig[开发手册](https://ziglang.org/documentation/master/)所述,它发生在以下情况:

- `switch` 的表达式
- `if` 的表达式
Expand Down
2 changes: 1 addition & 1 deletion course/basic/advanced_type/pointer.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ Zig 支持指针的加减运算,但建议在进行运算前,将指针转换

:::

## 多项指针和单向指针区别
## 多项指针和单项指针区别

本节专门解释单项指针和多项指针的区别。

Expand Down
2 changes: 1 addition & 1 deletion course/basic/advanced_type/slice.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ slice_2 类型为[]i32

在上面的例子中,我们从一个数组创建切片,其左边界为 0,右边界为变量 `len`

注意:如果切片的两个边界值都是编译期常量,编译器会将其优化为数组指针;但如果至少有一个边界值是运行时变量,那么它就是一个真正的切片
注意:如果切片的两个边界值都是编译期常量,编译器在类型推断时会将结果推断为数组指针类型(因为长度在编译期已知);但如果至少有一个边界值是运行时变量,那么结果类型就是切片(长度在运行时确定)

:::info 🅿️ 提示

Expand Down
2 changes: 1 addition & 1 deletion course/basic/advanced_type/string.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Unicode 码点字面量类型是 `comptime_int`。所有转义字符都可以在
:::info

字符串字面量中不能直接包含`<Tab>`字符(Zig 语言规范不允许在源代码中使用`<Tab>`)。但可以使用`\t`转义序列或`@embedFile`内建函数来实现类似的功能。
参考:[enum-backed address spaces](https://github.com/ziglang/zig-spec/issues/38]
参考:[enum-backed address spaces](https://github.com/ziglang/zig-spec/issues/38)

:::

Expand Down
2 changes: 1 addition & 1 deletion course/basic/advanced_type/struct.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@

然而,仅此还不够。在实际使用中,我们可能只初始化部分字段,而其他字段使用默认值。如果对结构体字段的默认值没有不变性要求,那么这种默认值方案已经足够使用。

但如果要求结构体字段的值具有默认不变性(即要么全部使用默认值,要么全部由使用者手动赋值),则可以采用以下方案:
但如果需要"全有或全无"(All-or-Nothing)的初始化策略——即要么全部使用默认值,要么全部由使用者手动赋值——则可以采用以下方案:

<<<@/code/release/struct.zig#all_default

Expand Down
24 changes: 6 additions & 18 deletions course/basic/basic_type/function.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Zig 的函数结构清晰,你可以一眼看出其组成部分。我们来用

<<<@/code/release/function.zig#max

其中 `comptime T: type` 可能对你来说比较陌生,这是[编译期](../../advanced/comptime.md)参数,它是实现鸭子类型(泛型)的关键语法。
其中 `comptime T: type` 可能对你来说比较陌生,这是[编译期](../../advanced/comptime.md)参数,它是实现泛型(参数多态)的关键语法。

:::

Expand Down Expand Up @@ -74,21 +74,11 @@ Zig 在这方面的处理是:原始类型(如整型、布尔)完全使用

以下是一些更加高级的用法,可以之后再学习!

### 闭包
### 模拟闭包模式

首先,我们来看维基百科对闭包的定义:
在计算机科学中,**闭包(Closure)** 是指一个函数与其引用的词法作用域(Lexical Scope)形成的组合——闭包允许一个函数访问其外部作用域中的变量,即使这个函数在其外部作用域之外被调用。其主要特性是能够“记住”其创建时的环境。

> 在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是在支持头等函数的编程语言中实现词法绑定的一种技术。
>
> 闭包在实现上是一个结构体,它存储了一个函数(通常是其入口地址)和一个关联的环境(相当于一个符号查找表)。
>
> 环境里是若干对符号和值的对应关系,它既要包括约束变量(该函数内部绑定的符号),也要包括自由变量(在函数外部定义但在函数内被引用),有些函数也可能没有自由变量。闭包跟函数最大的不同在于,当捕捉闭包的时候,它的自由变量会在捕捉时被确定,这样即便脱离了捕捉时的上下文,它也能照常运行。
>
> 捕捉时对于值的处理可以是值拷贝,也可以是名称引用,这通常由语言设计者决定,也可能由用户自行指定(如 C++)。
在 Zig 中,由于语言设计上的某些限制,我们无法像在某些其他语言中那样自由地使用闭包特性。

广义上讲,**闭包是指一个函数与其引用的词法作用域(lexical scope)形成的组合。简言之,闭包允许一个函数访问其外部作用域中的变量,即使这个函数在其外部作用域之外被调用。其主要特性是能够“记住”其创建时的环境。**
Zig 语言不允许在函数内部声明函数,也不允许直接创建匿名函数,因此 Zig **不支持传统意义上的闭包**(即捕获运行时自由变量的函数)。但我们可以通过编译期参数来**模拟**类似闭包的效果——需要注意的是,这种模式只能捕获编译期已知的值,与真正的闭包有本质区别。

在许多支持内存垃圾回收(GC)的语言中,它们的使用大概是这样的:

Expand All @@ -108,11 +98,9 @@ closure = outer_function()
closure() # 输出:Hello, World!
```

以上是一段 Python 代码。其中 `outer_function` 函数最终返回一个函数类型(实际上它返回了 `inner_function` 函数,但在解释执行时,它不再以 `inner_function` 的名称存在)。

Zig 语言不允许在函数内部声明函数,也不允许直接创建匿名函数。这两个特性在其他编程语言(例如 JavaScript、PowerQuery-M 等)中是实现闭包模式的常见方式。
以上是一段 Python 代码,其中 `inner_function` 捕获了外部函数的局部变量 `message`,这是典型的闭包行为。

因此,在 Zig 中实现闭包,通常需要其在编译期是已知的
在 Zig 中模拟类似效果,通常需要借助编译期参数

<<<@/code/release/function.zig#closure

Expand Down
6 changes: 2 additions & 4 deletions course/basic/basic_type/number.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ thread 2456131 panic: division by zero
值得注意的是,`comptime_float` 具有 `f128` 的精度和运算能力。
浮点字面量可以隐式转换为**任意浮点类型**。如果浮点字面量没有小数部分,它还可以隐式转换为**任意整数类型**
编译期已知的浮点字面量可以隐式转换为精度足以表示该值的浮点类型。如果浮点字面量没有小数部分,它还可以隐式转换为范围足以容纳该值的整数类型
浮点运算默认遵循 `Strict` 模式,但可以使用 `@setFloatMode(.Optimized)` 切换到 `Optimized` 模式。有关浮点运算模式的详细信息,请参见 [`@setFloatMode`](https://ziglang.org/documentation/master/#setFloatMode)。
Expand Down Expand Up @@ -179,9 +179,7 @@ pub fn main() void {
> 常见的加减乘除运算在此不再赘述,我们来聊聊 Zig 中独具特色的一些操作符。
<!-- TODO: 对等类型解析 -->
- `+|`:饱和加法。这涉及到[对等类型解析](../../advanced/type_cast.md#对等类型转换)。简单来说,加法结果不会超过该类型的最大值。例如,`u8` 类型的 255 加 1 后仍然是 255。
- `+|`:饱和加法。加法结果不会超过该类型的最大值。例如,`u8` 类型的 255 加 1 后仍然是 255。涉及多个操作数时,类型由[对等类型转换](../../advanced/type_cast.md#对等类型转换)规则决定。
- `-|`:饱和减法。与饱和加法类似,减法结果不会低于该类型的最小值。
- `*|`:饱和乘法。乘法结果不会超过该类型的最大值或最小值。
- `<<|`:饱和左移。左移结果不会超过该类型的最大值。
Expand Down
2 changes: 1 addition & 1 deletion course/basic/define-variable.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ outline: deep
## 变量声明

> 变量是用于在内存中存储值的命名空间
> 变量是与一个标识符(名称)绑定的、用于在内存中存储值的存储位置
在 Zig 中,我们使用 `var` 关键字来声明变量,其格式为 `var variable_name: Type = initial_value;`。以下是一个示例:

Expand Down
2 changes: 1 addition & 1 deletion course/basic/error_handle.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ outline: deep

`anyerror` 指的是全局错误集,它包含编译单元中的所有错误,是所有其他错误集的超集。

任何错误集都可以隐式转换为全局错误集,但反之则不然。`anyerror` 到其他错误集的转换需要显式进行,此时会增加一个语言级断言(language-level assert),要求该错误一定在目标错误集中存在
从类型系统的角度看,任何具体的错误集都是 `anyerror`**子类型**。因此,任何错误集都可以隐式转换(向上转型)为 `anyerror`,但反之则不然——`anyerror` 到具体错误集的转换需要显式进行,此时编译器会增加一个语言级断言(language-level assert),要求该错误确实存在于目标错误集中,否则会触发安全检查错误

::: warning ⚠️ 警告

Expand Down
2 changes: 1 addition & 1 deletion course/basic/optional_type.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ outline: deep

# 可选类型

## Overview
## 概述

在 Zig 中,为了在不损害效率的前提下提高代码安全性,可选类型是一个重要的解决方案。它的标志是 `?``?T` 表示该类型的值可以是 `null``T` 类型。

Expand Down
34 changes: 30 additions & 4 deletions course/basic/process_control/defer.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,38 @@ outline: deep

# defer

`defer` 将在当前作用域末尾执行表达式
`defer` 用于注册一个表达式(或代码块),使其在当前作用域结束时自动执行。这是 Zig 提供的一种**确定性资源管理**机制,功能上类似于 C++ 的 RAII(Resource Acquisition Is Initialization)或 Go 的 `defer`,用于确保资源(如内存、文件句柄、锁等)在离开作用域时得到正确释放

如果存在多个 `defer`,它们将会按照出栈方式执行。
## 执行顺序

如果存在多个 `defer`,它们将会按照**后进先出(LIFO)**的顺序执行——即最后注册的 `defer` 最先执行,类似栈的出栈顺序。

<<<@/code/release/defer.zig#Defer

`defer` 分别可以执行单个语句和一个块,并且如果控制流不经过 `defer`,则不会执行。
## 使用细节

- `defer` 可以执行单个语句,也可以执行一个代码块(由 `{}` 包裹)。
- 如果控制流没有经过 `defer` 语句(例如在 `defer` 之前就 `return` 了),则该 `defer` 不会被注册,自然也不会执行。
- `defer` 中的表达式在**作用域退出时**才会被求值,而非在 `defer` 语句出现的位置求值。

## 典型用法

`defer` 最常见的用途是配合内存分配器使用,确保分配的内存在作用域正常结束时被释放:

```zig
const allocator = std.heap.page_allocator;
const data = try allocator.alloc(u8, 100);
defer allocator.free(data);
// 使用 data...
// 当作用域正常退出时,data 会被释放
```

:::warning ⚠️ 注意
`defer` 仅在作用域**正常退出**时执行。如果函数因返回错误而退出,已注册的 `defer` **不会执行**。如果需要在错误返回时执行清理操作,请使用 `errdefer`。

因此在实际的资源管理中,通常需要 `defer` 和 `errdefer` 搭配使用——`defer` 负责正常路径的清理,`errdefer` 负责错误路径的清理。
:::

## `errdefer`

对应 `defer` 的还有 `errdefer`,具体见这里 [`errdefer`](/basic/error_handle#errdefer)。
对应 `defer` 的还有 `errdefer`,它仅在函数返回错误时才执行,具体见这里 [`errdefer`](/basic/error_handle#errdefer)。
Loading
Loading