From 6d0bcf370725557d4d6719730ff07c4228b96c5a Mon Sep 17 00:00:00 2001 From: Mathias Zhang Date: Fri, 16 Aug 2024 18:38:26 +0800 Subject: [PATCH 1/4] update: sync with upstream and minor fixes --- 02-language-overview-part1.md | 26 +++++---- 03-language-overview-part2.md | 26 +++++---- 04-style-guide.md | 4 +- 05-pointers.md | 32 +++++++++-- 06-stack-memory.md | 4 +- 07-heap-memory-and-allocator.md | 17 ++++-- 08-generics.md | 4 +- 09-coding-in-zig.md | 99 +++++++++++++++++++++------------ 8 files changed, 139 insertions(+), 73 deletions(-) diff --git a/02-language-overview-part1.md b/02-language-overview-part1.md index 9d53f1d..0b9eab9 100644 --- a/02-language-overview-part1.md +++ b/02-language-overview-part1.md @@ -2,7 +2,7 @@ # 语言概述 - 第 1 部分 -Zig 是一种强类型编译语言。它支持泛型,具有强大的编译时元编程功能,并且不包含垃圾收集器。许多人认为 Zig 是 C 的现代替代品。因此,该语言的语法与 C 类似,比较明显的就是以分号结尾的语句和以花括号分隔的块。 +Zig 是一种强类型编译语言。它支持泛型,具有强大的编译时元编程功能,并且**不包含**垃圾收集器。许多人认为 Zig 是 C 的现代替代品。因此,该语言的语法与 C 类似,比较明显的就是以分号结尾的语句和以花括号分隔的块。 Zig 代码如下所示: @@ -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 的知识点。因此,我们现在只需要掌握以下内容: @@ -151,7 +151,7 @@ pub const User = struct { }; ``` -> 由于我们的程序是单个文件,因此 `User` 仅在定义它的文件中使用,因此我们不需要将其设为 `pub` 。 +> 由于我们的程序是单个文件,因此 `User` 仅在定义它的文件中使用,因此我们不需要将其设为 `pub` 。但这样一来,我们就看不到如何将声明暴露给其他文件了。 结构字段以逗号终止,并且可以指定默认值: @@ -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}); } @@ -295,13 +295,14 @@ 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]; ``` `b` 现在是一个切片了。具体来说,它的类型是 `[]const i32`。你可以看到,切片的长度并不是类型的一部分,因为长度是运行时属性,而类型总是在编译时就完全已知。在创建切片时,我们可以省略上界,创建一个到要切分的对象(数组或切片)末尾的切片,例如 `const c = b[2..]`。 -> 如果我们将 `end` 声明为 `const` 那么它将成为编译时已知值,这将导致 `b` 是一个指向数组的指针,而不是切片。我觉得这有点令人困惑,但它并不是经常出现的东西,而且也不太难掌握。我很想在这一点上跳过它,但无法找到一种诚实的方法来避免这个细节。 +> 如果我们在执行 `const end: usize = 3` 时不进行增量,那么 `1..end` 就会成为 `b` 的编译时已知的长度,从而创建一个指向数组的指针,而不是一个切片。我觉得这有点令人困惑,但它并不是经常出现的东西,而且也不太难掌握。我很想在这一点上跳过它,但无法找到一种诚实的方法来避免这个细节。 学习 Zig 让我了解到,类型具有很强的描述性。它不仅仅是一个整数或布尔值,甚至是一个有符号的 32 位整数数组。类型还包含其他重要信息。我们已经讨论过长度是数组类型的一部分,许多示例也说明了可变性(const-ness)也是数组类型的一部分。例如,在上一个示例中,b 的类型是 `[]const i32`。你可以通过下面的代码来验证这一点: @@ -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)}); } @@ -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; } @@ -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..]; } @@ -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,具有任意数量参数的函数)。但它的编译器能根据传入的类型创建专门的函数,包括编译器自己推导和创建的类型。 diff --git a/03-language-overview-part2.md b/03-language-overview-part2.md index 03c1ef0..12be687 100644 --- a/03-language-overview-part2.md +++ b/03-language-overview-part2.md @@ -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; @@ -214,7 +221,6 @@ const Stage = enum { validate, awaiting_confirmation, confirmed, - completed, err, fn isComplete(self: Stage) bool { @@ -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` 子句时要小心,因为它会匹配任何新添加的枚举值,而这可能不是我们想要的行为。 diff --git a/04-style-guide.md b/04-style-guide.md index 4e52e9f..74f8059 100644 --- a/04-style-guide.md +++ b/04-style-guide.md @@ -31,7 +31,7 @@ pub fn main() void { // or - sum = add(8999, 2); + const sum = add(8999, 2); _ = sum; } @@ -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})}); diff --git a/05-pointers.md b/05-pointers.md index 2abc300..d118497 100644 --- a/05-pointers.md +++ b/05-pointers.md @@ -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 { @@ -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` 上: @@ -78,7 +93,7 @@ user -> ------------ (id: 1043368d0) ```zig pub fn main() void { - var user = User{ + const user = User{ .id = 1, .power = 100, }; @@ -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)}); @@ -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}); @@ -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}); @@ -161,6 +181,8 @@ pub const User = struct { 我们必须做两处改动。首先是用 `user` 的地址(即 `&user` )来调用 `levelUp`,而不是 `user`。这意味着我们的函数参数不再是 `User`,取而代之的是一个 `*User`,这是我们的第二处改动。 +我们不再需要通过 user.power += 0; 来强制修改 user 的那个丑陋的技巧了。最初,我们因为 user 是 var 类型而无法让代码编译,编译器告诉我们它从未被修改。我们以为编译器错了,于是通过强制修改来“糊弄”它。但正如我们现在所知道的,在 levelUp 中被修改的 user 是不同的;编译器是正确的。 + 现在,代码已按预期运行。虽然在函数参数和内存模型方面仍有许多微妙之处,但我们正在取得进展。现在也许是一个好时机来说明一下,除了特定的语法之外,这些都不是 Zig 所独有的。我们在这里探索的模型是最常见的,有些语言可能只是向开发者隐藏了很多细节,因此也就隐藏了灵活性。 ## 方法 @@ -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 diff --git a/06-stack-memory.md b/06-stack-memory.md index 9e6d7fb..6e538ce 100644 --- a/06-stack-memory.md +++ b/06-stack-memory.md @@ -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}); diff --git a/07-heap-memory-and-allocator.md b/07-heap-memory-and-allocator.md index 02c6cee..42ef450 100644 --- a/07-heap-memory-and-allocator.md +++ b/07-heap-memory-and-allocator.md @@ -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; } ``` @@ -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, @@ -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), }; @@ -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), @@ -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(); @@ -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}); @@ -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(); diff --git a/08-generics.md b/08-generics.md index cc699f3..f23b435 100644 --- a/08-generics.md +++ b/08-generics.md @@ -90,8 +90,8 @@ fn List(comptime T: type) type { .items = try allocator.alloc(T, 4), }; } - } -}; + }; +} ``` 上面的结构与 `IntList` 几乎完全相同,只是 `i64` 被替换成了 `T`。我们本可以叫它 `item_type`。不过,按照 Zig 的命名约定,`type` 类型的变量使用 `PascalCase` 风格。 diff --git a/09-coding-in-zig.md b/09-coding-in-zig.md index 9bcc516..e2c79e0 100644 --- a/09-coding-in-zig.md +++ b/09-coding-in-zig.md @@ -128,11 +128,11 @@ pub fn main() !void { var buf: [30]u8 = undefined; try stdout.print("Please enter a name: ", .{}); if (try stdin.readUntilDelimiterOrEof(&buf, '\n')) |line| { - var name: []const u8 = line; + var name = line; // Windows平台换行以`\r\n`结束 // 所以需要截取\r以获取控制台输入字符 if (builtin.os.tag == .windows) { - name = std.mem.trimRight(u8, line[0 .. line.len - 1], "\r"); + name = @constCast(std.mem.trimRight(u8, name, "\r")); } if (name.len == 0) { @@ -247,9 +247,9 @@ pub fn main() !void { var buf: [30]u8 = undefined; try stdout.print("Please enter a name: ", .{}); if (try stdin.readUntilDelimiterOrEof(&buf, '\n')) |line| { - var name: []const u8 = line; + var name = line; if (builtin.os.tag == .windows) { - name = std.mem.trimRight(u8, line[0 .. line.len - 1], "\r"); + name = @constCast(std.mem.trimRight(u8, name, "\r")); } if (name.len == 0) { @@ -438,7 +438,7 @@ pub fn build(b: *std.Build) !void { .name = "learning", .target = target, .optimize = optimize, - .root_source_file = .{ .path = "learning.zig" }, + .root_source_file = b.path("learning.zig"), }); b.installArtifact(exe); } @@ -470,7 +470,7 @@ run_step.dependOn(&run_cmd.step); const tests = b.addTest(.{ .target = target, .optimize = optimize, - .root_source_file = .{ .path = "learning.zig" }, + .root_source_file = b.path("learning.zig"), }); const test_cmd = b.addRunArtifact(tests); @@ -489,7 +489,7 @@ test "dummy build test" { 现在运行 `zig build test`时,应该会出现测试失败。如果你修复了测试,并再次运行 `zig build test`,你将不会得到任何输出。默认情况下,Zig 的测试运行程序只在失败时输出结果。如果你像我一样,无论成功还是失败,都想要一份总结,那就使用 `zig build test --summary all`。 -这是启动和运行构建系统所需的最低配置。但是请放心,如果你需要构建你的程序,Zig 内置的功能大概率能覆盖你的需求。最后,你可以(也应该)在你的项目根目录下使用 `zig init-exe` 或 `zig init-lib`,让 Zig 为你创建一个文档齐全的 `build.zig` 文件。 +这是启动和运行构建系统所需的最低配置。但是请放心,如果你需要构建你的程序,Zig 内置的功能大概率能覆盖你的需求。最后,你可以(也应该)在你的项目根目录下使用 `zig init`,让 Zig 为你创建一个文档齐全的 `build.zig` 文件。 ## 第三方依赖 @@ -536,7 +536,7 @@ pub fn build(b: *std.Build) !void { const tests = b.addTest(.{ .target = target, .optimize = optimize, - .root_source_file = .{ .path = "calc.zig" }, + .root_source_file = b.path("calc.zig"), }); const test_cmd = b.addRunArtifact(tests); @@ -555,32 +555,59 @@ pub fn build(b: *std.Build) !void { // 即调用 addExecutable 之前。 const calc_module = b.addModule("calc", .{ - .source_file = .{ .path = "PATH_TO_CALC_PROJECT/calc.zig" }, + .root_source_file = b.path("PATH_TO_CALC_PROJECT/calc.zig"), }); ``` -您需要调整 `calc.zig` 的路径。现在,我们需要将此模块添加到现有的 `exe` 和 `tests` 中: +你需要调整 `calc.zig` 的路径。现在,我们需要将这个模块添加到现有的 `exe` 和 `tests` 变量中。由于我们的 build.zig 变得越来越复杂,我们将尝试稍微组织一下: ```zig -const exe = b.addExecutable(.{ - .name = "learning", - .target = target, - .optimize = optimize, - .root_source_file = .{ .path = "learning.zig" }, -}); -// 添加这些代码 -exe.addModule("calc", calc_module); -b.installArtifact(exe); +const std = @import("std"); -.... +pub fn build(b: *std.Build) !void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); -const tests = b.addTest(.{ - .target = target, - .optimize = optimize, - .root_source_file = .{ .path = "learning.zig" }, -}); -// 添加这行代码 -tests.addModule("calc", calc_module); + const calc_module = b.addModule("calc", .{ + .root_source_file = b.path("PATH_TO_CALC_PROJECT/calc.zig"), + }); + + { + // 设置我们的 "run" 命令。 + + const exe = b.addExecutable(.{ + .name = "learning", + .target = target, + .optimize = optimize, + .root_source_file = b.path("learning.zig"), + }); + // 添加这些代码 + exe.root_module.addImport("calc", calc_module); + b.installArtifact(exe); + + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + + const run_step = b.step("run", "Start learning!"); + run_step.dependOn(&run_cmd.step); + } + + { + // 设置我们的 "test" 命令。 + const tests = b.addTest(.{ + .target = target, + .optimize = optimize, + .root_source_file = b.path("learning.zig"), + }); + // 添加这行代码 + tests.root_module.addImport("calc", calc_module); + + const test_cmd = b.addRunArtifact(tests); + test_cmd.step.dependOn(b.getInstallStep()); + const test_step = b.step("test", "Run the tests"); + test_step.dependOn(&test_cmd.step); + } +} ``` 现在,可以在项目中 `@import("calc")`: @@ -595,7 +622,7 @@ calc.add(1, 2); ```zig _ = b.addModule("calc", .{ - .source_file = .{ .path = "calc.zig" }, + .root_source_file = b.path("calc.zig"), }); ``` @@ -606,24 +633,25 @@ _ = b.addModule("calc", .{ ```zig .{ .name = "learning", + .paths = .{""}, .version = "0.0.0", .dependencies = .{ .calc = .{ - .url = "https://github.com/karlseguin/calc.zig/archive/e43c576da88474f6fc6d971876ea27effe5f7572.tar.gz", + .url = "https://github.com/karlseguin/calc.zig/archive/d1881b689817264a5644b4d6928c73df8cf2b193.tar.gz", .hash = "12ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" }, }, } ``` -该文件中有两个可疑值,第一个是 url 中的 e43c576da88474f6fc6d971876ea27effe5f7572。这只是 git 提交的哈希值。第二个是哈希值。据我所知,目前还没有很好的方法来告诉我们这个值应该是多少,所以我们暂时使用一个假值。 +该文件中有两个可疑值,第一个是 url 中的 d1881b689817264a5644b4d6928c73df8cf2b193<。这只是 git 提交的哈希值。第二个是哈希值。据我所知,目前还没有很好的方法来告诉我们这个值应该是多少,所以我们暂时使用一个假值。 要使用这一依赖关系,我们需要对 `build.zig` 进行一处修改: ```zig // 将这些代码: const calc_module = b.addModule("calc", .{ - .source_file = .{ .path = "calc/calc.zig" }, + .root_source_file = b.path("calc/calc.zig"), }); // 替换成: @@ -636,12 +664,11 @@ const calc_module = calc_dep.module("calc"); 如果你尝试运行 `zig build test`,应该会看到一个错误: ```bash -error: hash mismatch: -expected: -12ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, +hash mismatch: manifest declares +122053da05e0c9348d91218ef015c8307749ef39f8e90c208a186e5f444e818672da -found: -122053da05e0c9348d91218ef015c8307749ef39f8e90c208a186e5f444e818672d4 +but the fetched package has +122036b1948caa15c2c9054286b3057877f7b152a5102c9262511bf89554dc836ee5 ``` 将正确的哈希值复制并粘贴回 `build.zig.zon`,然后再次尝试运行 `zig build test`,现在一切应该都正常了。 From e85fc853418373a420119f55f4c974a6ec8bfe8c Mon Sep 17 00:00:00 2001 From: Jiacai Liu Date: Sun, 18 Aug 2024 10:44:15 +0800 Subject: [PATCH 2/4] Update 02-language-overview-part1.md --- 02-language-overview-part1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/02-language-overview-part1.md b/02-language-overview-part1.md index 0b9eab9..87462f1 100644 --- a/02-language-overview-part1.md +++ b/02-language-overview-part1.md @@ -302,7 +302,7 @@ const b = a[1..end]; `b` 现在是一个切片了。具体来说,它的类型是 `[]const i32`。你可以看到,切片的长度并不是类型的一部分,因为长度是运行时属性,而类型总是在编译时就完全已知。在创建切片时,我们可以省略上界,创建一个到要切分的对象(数组或切片)末尾的切片,例如 `const c = b[2..]`。 -> 如果我们在执行 `const end: usize = 3` 时不进行增量,那么 `1..end` 就会成为 `b` 的编译时已知的长度,从而创建一个指向数组的指针,而不是一个切片。我觉得这有点令人困惑,但它并不是经常出现的东西,而且也不太难掌握。我很想在这一点上跳过它,但无法找到一种诚实的方法来避免这个细节。 +> 如果我们将 `end` 声明为 `const` 那么它将成为编译时已知值,这将导致 `b` 是一个指向数组的指针,而不是切片。我觉得这有点令人困惑,但它并不是经常出现的东西,而且也不太难掌握。我很想在这一点上跳过它,但无法找到一种诚实的方法来避免这个细节。 学习 Zig 让我了解到,类型具有很强的描述性。它不仅仅是一个整数或布尔值,甚至是一个有符号的 32 位整数数组。类型还包含其他重要信息。我们已经讨论过长度是数组类型的一部分,许多示例也说明了可变性(const-ness)也是数组类型的一部分。例如,在上一个示例中,b 的类型是 `[]const i32`。你可以通过下面的代码来验证这一点: From b6439f0f6b4436262d6f3f14392b8d805d510717 Mon Sep 17 00:00:00 2001 From: Jiacai Liu Date: Sun, 18 Aug 2024 10:54:11 +0800 Subject: [PATCH 3/4] Apply suggestions from code review --- 05-pointers.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/05-pointers.md b/05-pointers.md index d118497..77bbadd 100644 --- a/05-pointers.md +++ b/05-pointers.md @@ -77,7 +77,7 @@ user -> ------------ (id) 请记住,我们的`user`也有一个类型。该类型告诉我们 `id` 是一个 64 位整数,`power` 是一个 32 位整数。有了对数据起始位置的引用和类型,编译器就可以将 `user.power` 转换为:访问位置在结构体第 64 位上的一个 32 位整数。这就是变量的威力,它们可以引用内存,并包含以有意义的方式理解和操作内存所需的类型信息。 -> 默认情况下,Zig 不保证结构的内存布局。它可以按字母顺序、大小升序或插入填充(padding)某些字段。只要它能正确翻译我们的代码,它就可以为所欲为。这种自由度可以实现某些优化。只有在声明 `packed struct`时,我们才能获得内存布局的有力保证。我们还可以创建一个 extern struct,这样可以保证内存布局与 C 应用程序二进制接口 (ABI) 匹配。尽管如此,我们对`user`的可视化还是合理而有用的。 +> 默认情况下,Zig 不保证结构的内存布局。它可以按字母顺序、大小升序或插入填充(padding)某些字段。只要它能正确翻译我们的代码,它就可以为所欲为。这种自由度可以实现某些优化。只有在声明 `packed struct`时,我们才能获得内存布局的有力保证。我们还可以创建一个 `extern struct`,这样可以保证内存布局与 C 应用程序二进制接口 (ABI) 匹配。尽管如此,我们对`user`的可视化还是合理而有用的。 下面是一个稍有不同的可视化效果,其中包括内存地址。这些数据的起始内存地址是我想出来的一个随机地址。这是`user`变量引用的内存地址,也是第一个字段 `id` 的值所在的位置。由于 `id` 是一个 64 位整数,需要 8 字节内存。因此,`power` 必须位于 `$start_address + 8` 上: @@ -181,7 +181,7 @@ pub const User = struct { 我们必须做两处改动。首先是用 `user` 的地址(即 `&user` )来调用 `levelUp`,而不是 `user`。这意味着我们的函数参数不再是 `User`,取而代之的是一个 `*User`,这是我们的第二处改动。 -我们不再需要通过 user.power += 0; 来强制修改 user 的那个丑陋的技巧了。最初,我们因为 user 是 var 类型而无法让代码编译,编译器告诉我们它从未被修改。我们以为编译器错了,于是通过强制修改来“糊弄”它。但正如我们现在所知道的,在 levelUp 中被修改的 user 是不同的;编译器是正确的。 +我们不再需要通过 `user.power += 0;` 来强制修改 user 的那个丑陋的技巧了。最初,我们因为 user 是 var 类型而无法让代码编译,编译器告诉我们它从未被修改。我们以为编译器错了,于是通过强制修改来“糊弄”它。但正如我们现在所知道的,在 levelUp 中被修改的 user 是不同的;编译器是正确的。 现在,代码已按预期运行。虽然在函数参数和内存模型方面仍有许多微妙之处,但我们正在取得进展。现在也许是一个好时机来说明一下,除了特定的语法之外,这些都不是 Zig 所独有的。我们在这里探索的模型是最常见的,有些语言可能只是向开发者隐藏了很多细节,因此也就隐藏了灵活性。 From 3ae2ea969ac3978e840591dfb7b65d1bb131896d Mon Sep 17 00:00:00 2001 From: Jiacai Liu Date: Sun, 18 Aug 2024 10:55:10 +0800 Subject: [PATCH 4/4] Update 05-pointers.md --- 05-pointers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/05-pointers.md b/05-pointers.md index 77bbadd..0577f0b 100644 --- a/05-pointers.md +++ b/05-pointers.md @@ -34,7 +34,7 @@ pub const User = struct { }; ``` -那是个不太友善的把戏;代码无法编译:_局部变量从未被修改_。这是指 `main` 函数中的 `user` 变量。一个从未被修改的变量必须声明为 const。你可能会想:但在 `levelUp` 函数中我们确实修改了 `user`,这怎么回事?让我们假设 Zig 编译器弄错了,并试着糊弄它。我们将强制让编译器看到 `user` 确实被修改了: +这里我设置了一个陷阱,此段代码将无法编译:_局部变量从未被修改_。这是指 `main` 函数中的 `user` 变量。一个从未被修改的变量必须声明为 const。你可能会想:但在 `levelUp` 函数中我们确实修改了 `user`,这怎么回事?让我们假设 Zig 编译器弄错了,并试着糊弄它。我们将强制让编译器看到 `user` 确实被修改了: ```zig const std = @import("std");