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

Commit

Permalink
update: sync with upstream and minor fixes (#37)
Browse files Browse the repository at this point in the history
* update: sync with upstream and minor fixes

* Update 02-language-overview-part1.md

* Apply suggestions from code review

* Update 05-pointers.md

---------

Co-authored-by: Jiacai Liu <dev@liujiacai.net>
  • Loading branch information
zzzsyyy and jiacai2050 authored Aug 18, 2024
1 parent 9654143 commit 9d3d457
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 72 deletions.
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

0 comments on commit 9d3d457

Please sign in to comment.