Skip to content
This repository has been archived by the owner on Aug 24, 2024. It is now read-only.

update: sync with upstream and minor fixes #37

Merged
merged 4 commits into from
Aug 18, 2024
Merged
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
24 changes: 14 additions & 10 deletions 02-language-overview-part1.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# 语言概述 - 第 1 部分

Zig 是一种强类型编译语言。它支持泛型,具有强大的编译时元编程功能,并且不包含垃圾收集器。许多人认为 Zig 是 C 的现代替代品。因此,该语言的语法与 C 类似,比较明显的就是以分号结尾的语句和以花括号分隔的块。
Zig 是一种强类型编译语言。它支持泛型,具有强大的编译时元编程功能,并且**不包含**垃圾收集器。许多人认为 Zig 是 C 的现代替代品。因此,该语言的语法与 C 类似,比较明显的就是以分号结尾的语句和以花括号分隔的块。

Zig 代码如下所示:

Expand Down Expand Up @@ -73,7 +73,7 @@ pub const User = struct {
```zig
const user = @import("models/user.zig");
const User = user.User;
const MAX_POWER = user.MAX_POWER
const MAX_POWER = user.MAX_POWER;
```

此时,你可能会有更多的困惑。在上面的代码片段中,`user` 是什么?我们还没有看到它,如果使用 `var` 来代替 `const` 会有什么不同呢?或者你可能想知道如何使用第三方库。这些都是好问题,但要回答这些问题,需要掌握更多 Zig 的知识点。因此,我们现在只需要掌握以下内容:
Expand Down Expand Up @@ -151,7 +151,7 @@ pub const User = struct {
};
```

> 由于我们的程序是单个文件,因此 `User` 仅在定义它的文件中使用,因此我们不需要将其设为 `pub` 。
> 由于我们的程序是单个文件,因此 `User` 仅在定义它的文件中使用,因此我们不需要将其设为 `pub` 。但这样一来,我们就看不到如何将声明暴露给其他文件了。

结构字段以逗号终止,并且可以指定默认值:

Expand Down Expand Up @@ -179,7 +179,7 @@ pub const User = struct {

pub const SUPER_POWER = 9000;

fn diagnose(user: User) void {
pub fn diagnose(user: User) void {
if (user.power >= SUPER_POWER) {
std.debug.print("it's over {d}!!!", .{SUPER_POWER});
}
Expand Down Expand Up @@ -295,7 +295,8 @@ const b = a[1..4];

```zig
const a = [_]i32{1, 2, 3, 4, 5};
var end: usize = 4;
var end: usize = 3;
end += 1;
const b = a[1..end];
```

Expand All @@ -310,7 +311,8 @@ const std = @import("std");

pub fn main() void {
const a = [_]i32{1, 2, 3, 4, 5};
var end: usize = 4;
var end: usize = 3;
end += 1;
const b = a[1..end];
std.debug.print("{any}", .{@TypeOf(b)});
}
Expand All @@ -325,14 +327,15 @@ pub fn main() void {
var b = a[1..end];
```

但你会得到同样的错误,为什么?作为提示,`b` 的类型是什么,或者更通俗地说,`b` 是什么?切片是指向数组(部分)的长度和指针。切片的类型总是从底层数组派生出来的。无论 `b` 是否声明为 `const`,底层数组都是 `[5]const i32` 类型,因此 b 必须是 `[]const i32` 类型。如果我们想写入 `b`,就需要将 `a` 从 `const` 变为 `var`。
但你会得到同样的错误,为什么?作为提示,`b` 的类型是什么,或者更通俗地说,`b` 是什么?切片是指向数组(部分)的长度和指针。切片的类型总是从它所切分的对象派生出来的。无论 `b` 是否声明为 `const`,都是一个 `[5]const i32` 的切片,因此 b 必须是 `[]const i32` 类型。如果我们想写入 `b`,就需要将 `a` 从 `const` 变为 `var`。

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

pub fn main() void {
var a = [_]i32{1, 2, 3, 4, 5};
var end: usize = 4;
var end: usize = 3;
end += 1;
const b = a[1..end];
b[2] = 99;
}
Expand All @@ -345,7 +348,8 @@ const std = @import("std");

pub fn main() void {
var a = [_]i32{1, 2, 3, 4, 5};
var end: usize = 4;
var end: usize = 3;
end += 1;
const b = a[1..end];
b = b[1..];
}
Expand Down Expand Up @@ -437,6 +441,6 @@ pub fn main() void {
struct{comptime year: comptime_int = 2023, comptime month: comptime_int = 8}
```

在这里,我们给匿名结构的字段取名为 `year` 和 `month`。在原始代码中,我们没有这样做。在这种情况下,字段名会自动生成 0、1、2 等。`print` 函数希望结构中包含此类字段,并使用字符串格式中的序号位置来获取适当的参数。
在这里,我们给匿名结构的字段取名为 `year` 和 `month`。在原始代码中,我们没有这样做。在这种情况下,字段名会自动生成 0、1、2 等。虽然它们都是匿名结构字面形式的示例,但没有字段名称的结构通常被称为“元组”(tuple)。`print` 函数希望接收一个元组,并使用字符串格式中的序号位置来获取适当的参数。

Zig 没有函数重载,也没有可变函数(vardiadic,具有任意数量参数的函数)。但它的编译器能根据传入的类型创建专门的函数,包括编译器自己推导和创建的类型。
26 changes: 16 additions & 10 deletions 03-language-overview-part2.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,24 +132,31 @@ fn indexOf(haystack: []const u32, needle: u32) ?usize {
}
```

> 这是对可空类型的初步了解。

范围的末端由 `haystack` 的长度推断,不过我们也可以写出 `0..haystack.len`,但这没有必要。`for` 循环不支持常见的 `init; compare; step` 风格,对于这种情况,可以使用 `while`。

因为 `while` 比较简单,形式如下:`while (condition) { }`,这有利于更好地控制迭代。例如,在计算字符串中转义序列的数量时,我们需要将迭代器递增 2 以避免重复计算 `\\`:

```zig
var i: usize = 0;
var escape_count: usize = 0;
while (i < src.len) {
if (src[i] == '\\') {
i += 2;
escape_count += 1;
} else {
i += 1;
{
var i: usize = 0;
// 反斜杠用作转义字符,因此我们需要用一个反斜杠来转义它。
while (i < src.len) {
if (src[i] == '\\') {
i += 2;
escape_count += 1;
} else {
i += 1;
}
}
}
```

`while` 可以包含 `else` 子句,当条件为假时执行 `else` 子句。它还可以接受在每次迭代后要执行的语句。在 `for` 支持遍历多个序列之前,这一功能很常用。上述语句可写成
我们在临时变量 `i` 和 `while` 循环周围添加了一个显式的代码块。这缩小了 `i` 的作用范围。这样的代码块可能会很有用,尽管在这个例子中可能有些过度。不过,上述例子已经是 Zig 中最接近传统的 `for(init; compare; step)` 循环的写法了。

`while` 可以包含 `else` 子句,当条件为假时执行 `else` 子句。它还可以接受在每次迭代后要执行的语句。多个语句可以用 ; 分隔。在 `for` 支持遍历多个序列之前,这一功能很常用。上述语句可写成

```zig
var i: usize = 0;
Expand Down Expand Up @@ -214,7 +221,6 @@ const Stage = enum {
validate,
awaiting_confirmation,
confirmed,
completed,
err,

fn isComplete(self: Stage) bool {
Expand All @@ -225,7 +231,7 @@ const Stage = enum {

> 如果需要枚举的字符串表示,可以使用内置的 `@tagName(enum)` 函数。

回想一下,结构类型可以使用 `.{...}` 符号根据其赋值或返回类型来推断。在上面,我们看到枚举类型是根据与 `self` 的比较推导出来的,而 `self` 的类型是 `Stage`。我们本可以明确地写成:`return self == Stage.confirmed` 或 `self == Stage.err`。但是,在处理枚举时,你经常会看到通过 `.$value` 这种省略具体类型的情况。
回想一下,结构类型可以使用 `.{...}` 符号根据其赋值或返回类型来推断。在上面,我们看到枚举类型是根据与 `self` 的比较推导出来的,而 `self` 的类型是 `Stage`。我们本可以明确地写成:`return self == Stage.confirmed` 或 `self == Stage.err`。但是,在处理枚举时,你经常会看到通过 `.$value` 这种省略具体类型的情况。这被称为*枚举字面量*。

`switch` 的穷举性质使它能与枚举很好地搭配,因为它能确保你处理了所有可能的情况。不过在使用 `switch` 的 `else` 子句时要小心,因为它会匹配任何新添加的枚举值,而这可能不是我们想要的行为。

Expand Down
4 changes: 2 additions & 2 deletions 04-style-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub fn main() void {

// or

sum = add(8999, 2);
const sum = add(8999, 2);
_ = sum;
}

Expand Down Expand Up @@ -74,7 +74,7 @@ fn read(stream: std.net.Stream) ![]const u8 {

Zig 代码采用 4 个空格进行缩进。我个人会因为客观上更方便,使用`tab`键。

Zig 的函数名采用了驼峰命名法(camelCase),而变量名会采用小写加下划线(snake case)的命名方式。类型则采用的是 PascalCase 风格。除了这三条规则外,一个有趣的交叉规则是,如果一个变量表示一个类型,或者一个函数返回一个类型,那么这个变量或者函数遵循 PascalCase。在之前的章节中,其实已经见到了这个例子,不过,可能你没有注意到:
Zig 的函数名采用了驼峰命名法(camelCase,而变量名会采用小写加下划线(snake case的命名方式。类型则采用的是 PascalCase 风格。除了这三条规则外,一个有趣的交叉规则是,如果一个变量表示一个类型,或者一个函数返回一个类型,那么这个变量或者函数遵循 PascalCase。在之前的章节中,其实已经见到了这个例子,不过,可能你没有注意到:

```zig
std.debug.print("{any}\n", .{@TypeOf(.{.year = 2023, .month = 8})});
Expand Down
32 changes: 27 additions & 5 deletions 05-pointers.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,22 @@ pub const User = struct {
};
```

这是一个不怀好意的小把戏;代码无法编译:**不能赋值给常量**。我们在第一部分中看到函数参数是常量,因此 `user.power += 1` 是无效的。为了解决这个错误,我们可以将 `levelUp` 函数改为
这里我设置了一个陷阱,此段代码将无法编译:_局部变量从未被修改_。这是指 `main` 函数中的 `user` 变量。一个从未被修改的变量必须声明为 const。你可能会想:但在 `levelUp` 函数中我们确实修改了 `user`,这怎么回事?让我们假设 Zig 编译器弄错了,并试着糊弄它。我们将强制让编译器看到 `user` 确实被修改了:

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

pub fn main() void {
var user = User{
.id = 1,
.power = 100,
};
user.power += 0;

// 代码的其余部分保持不变。
```

现在我们在 `levelUp` 中遇到了一个错误:**不能赋值给常量**。我们在第一部分中看到函数参数是常量,因此 `user.power += 1` 是无效的。为了解决这个错误,我们可以将 `levelUp` 函数改为

```zig
fn levelUp(user: User) void {
Expand Down Expand Up @@ -62,7 +77,7 @@ user -> ------------ (id)

请记住,我们的`user`也有一个类型。该类型告诉我们 `id` 是一个 64 位整数,`power` 是一个 32 位整数。有了对数据起始位置的引用和类型,编译器就可以将 `user.power` 转换为:访问位置在结构体第 64 位上的一个 32 位整数。这就是变量的威力,它们可以引用内存,并包含以有意义的方式理解和操作内存所需的类型信息。

> 默认情况下,Zig 不保证结构的内存布局。它可以按字母顺序、大小升序或插入填充(padding)某些字段。只要它能正确翻译我们的代码,它就可以为所欲为。这种自由度可以实现某些优化。只有在声明 `packed struct`时,我们才能获得内存布局的有力保证。尽管如此,我们对`user`的可视化还是合理而有用的。
> 默认情况下,Zig 不保证结构的内存布局。它可以按字母顺序、大小升序或插入填充(padding)某些字段。只要它能正确翻译我们的代码,它就可以为所欲为。这种自由度可以实现某些优化。只有在声明 `packed struct`时,我们才能获得内存布局的有力保证。我们还可以创建一个 `extern struct`,这样可以保证内存布局与 C 应用程序二进制接口 (ABI) 匹配。尽管如此,我们对`user`的可视化还是合理而有用的。

下面是一个稍有不同的可视化效果,其中包括内存地址。这些数据的起始内存地址是我想出来的一个随机地址。这是`user`变量引用的内存地址,也是第一个字段 `id` 的值所在的位置。由于 `id` 是一个 64 位整数,需要 8 字节内存。因此,`power` 必须位于 `$start_address + 8` 上:

Expand All @@ -78,7 +93,7 @@ user -> ------------ (id: 1043368d0)

```zig
pub fn main() void {
var user = User{
const user = User{
.id = 1,
.power = 100,
};
Expand All @@ -102,6 +117,7 @@ pub fn main() void {
.id = 1,
.power = 100,
};
user.power += 0;

const user_p = &user;
std.debug.print("{any}\n", .{@TypeOf(user_p)});
Expand All @@ -112,10 +128,11 @@ pub fn main() void {

```zig
pub fn main() void {
const user = User{
var user = User{
.id = 1,
.power = 100,
};
user.power += 0;

// added this
std.debug.print("main: {*}\n", .{&user});
Expand Down Expand Up @@ -143,6 +160,9 @@ pub fn main() void {
.power = 100,
};

// no longer needed
// user.power += 1;

// user -> &user
levelUp(&user);
std.debug.print("User {d} has power of {d}\n", .{user.id, user.power});
Expand All @@ -161,6 +181,8 @@ pub const User = struct {

我们必须做两处改动。首先是用 `user` 的地址(即 `&user` )来调用 `levelUp`,而不是 `user`。这意味着我们的函数参数不再是 `User`,取而代之的是一个 `*User`,这是我们的第二处改动。

我们不再需要通过 `user.power += 0;` 来强制修改 user 的那个丑陋的技巧了。最初,我们因为 user 是 var 类型而无法让代码编译,编译器告诉我们它从未被修改。我们以为编译器错了,于是通过强制修改来“糊弄”它。但正如我们现在所知道的,在 levelUp 中被修改的 user 是不同的;编译器是正确的。

现在,代码已按预期运行。虽然在函数参数和内存模型方面仍有许多微妙之处,但我们正在取得进展。现在也许是一个好时机来说明一下,除了特定的语法之外,这些都不是 Zig 所独有的。我们在这里探索的模型是最常见的,有些语言可能只是向开发者隐藏了很多细节,因此也就隐藏了灵活性。

## 方法
Expand Down Expand Up @@ -321,7 +343,7 @@ const std = @import("std");

pub fn main() void {
var name = [4]u8{'G', 'o', 'k', 'u'};
var user = User{
const user = User{
.id = 1,
.power = 100,
// slice it, [4]u8 -> []u8
Expand Down
4 changes: 2 additions & 2 deletions 06-stack-memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ main: user -> ------------- (id: 1043368d0)
const std = @import("std");

pub fn main() void {
var user1 = User.init(1, 10);
var user2 = User.init(2, 20);
const user1 = User.init(1, 10);
const user2 = User.init(2, 20);

std.debug.print("User {d} has power of {d}\n", .{user1.id, user1.power});
std.debug.print("User {d} has power of {d}\n", .{user2.id, user2.power});
Expand Down
17 changes: 12 additions & 5 deletions 07-heap-memory-and-allocator.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ pub fn main() !void {

fn getRandomCount() !u8 {
var seed: u64 = undefined;
try std.os.getrandom(std.mem.asBytes(&seed));
var random = std.rand.DefaultPrng.init(seed);
try std.posix.getrandom(std.mem.asBytes(&seed));
var random = std.Random.DefaultPrng.init(seed);
return random.random().uintAtMost(u8, 5) + 5;
}
```
Expand Down Expand Up @@ -214,7 +214,7 @@ pub const User = struct {
// 我们的返回类型改变了,因为 init 现在可以失败了
// *User -> !*User
fn init(allocator: std.mem.Allocator, id: u64, power: i32) !*User{
var user = try allocator.create(User);
const user = try allocator.create(User);
user.* = .{
.id = id,
.power = power,
Expand Down Expand Up @@ -423,7 +423,7 @@ self.allocator.free(self.items);

```zig
fn parse(allocator: Allocator, input: []const u8) !Something {
var state = State{
const state = State{
.buf = try allocator.alloc(u8, 512),
.nesting = try allocator.alloc(NestType, 10),
};
Expand All @@ -448,7 +448,7 @@ fn parse(allocator: Allocator, input: []const u8) !Something {
// the allocator we'll use internally
const aa = arena.allocator();

var state = State{
const state = State{
// we're using aa here!
.buf = try aa.alloc(u8, 512),

Expand Down Expand Up @@ -514,6 +514,8 @@ const std = @import("std");
pub fn main() !void {
var buf: [150]u8 = undefined;
var fa = std.heap.FixedBufferAllocator.init(&buf);

// this will free all memory allocate with this allocator
defer fa.reset();

const allocator = fa.allocator();
Expand All @@ -523,6 +525,9 @@ pub fn main() !void {
.above = true,
.last_param = "are options",
}, .{.whitespace = .indent_2});

// We can free this allocation, but since we know that our allocator is
// a FixedBufferAllocator, we can rely on the above `defer fa.reset()`
defer allocator.free(json);

std.debug.print("{s}\n", .{json});
Expand Down Expand Up @@ -569,6 +574,8 @@ pub fn main() !void {
动态分配的另一个可行替代方案是将数据流传输到 `std.io.Writer`。与我们的 `Allocator` 一样,`Writer` 也是被许多具体类型实现的接口。上面,我们使用 `stringifyAlloc` 将 JSON 序列化为动态分配的字符串。我们本可以使用 `stringify` 将其写入到一个 Writer 中:

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

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

Expand Down
4 changes: 2 additions & 2 deletions 08-generics.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ fn List(comptime T: type) type {
.items = try allocator.alloc(T, 4),
};
}
}
};
};
}
```

上面的结构与 `IntList` 几乎完全相同,只是 `i64` 被替换成了 `T`。我们本可以叫它 `item_type`。不过,按照 Zig 的命名约定,`type` 类型的变量使用 `PascalCase` 风格。
Expand Down
Loading