wip: 2024 edition

This commit is contained in:
KaiserY 2025-05-15 23:52:16 +08:00
parent 2a6921863c
commit 7bf2039ec4
6 changed files with 52 additions and 39 deletions

View File

@ -26,7 +26,7 @@ jobs:
run: |
mkdir bin
curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.49/mdbook-v0.4.49-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=bin
curl -sSL https://github.com/KaiserY/mdbook-typst-pdf/releases/download/v0.6.1/mdbook-typst-pdf-x86_64-unknown-linux-musl.tar.xz | tar -xJ --directory=bin --strip-components 1
curl -sSL https://github.com/KaiserY/mdbook-typst-pdf/releases/download/v0.6.2/mdbook-typst-pdf-x86_64-unknown-linux-musl.tar.xz | tar -xJ --directory=bin --strip-components 1
echo "$(pwd)/bin" >> ${GITHUB_PATH}
- name: Install font
run: |

View File

@ -58,7 +58,7 @@
### 复制值的 `Clone``Copy`
`Clone` trait 可以明确地创建一个值的深拷贝deep copy复制过程可能包含任意代码的执行以及堆上数据的复制。查阅第四章 [“变量与数据交互的方式(二):克隆”][ways-variables-and-data-interact-clone] 以获取有关 `Clone` 的更多信息。
`Clone` trait 可以明确地创建一个值的深拷贝deep copy复制过程可能包含任意代码的执行以及堆上数据的复制。查阅第四章 [“使用克隆的变量与数据交互”][ways-variables-and-data-interact-clone] 以获取有关 `Clone` 的更多信息。
派生 `Clone` 实现了 `clone` 方法,其为整个的类型实现时,在类型的每一部分上调用了 `clone` 方法。这意味着类型中所有字段或值也必须实现了 `Clone`,这样才能够派生 `Clone`
@ -90,5 +90,5 @@
[creating-instances-from-other-instances-with-struct-update-syntax]: ch05-01-defining-structs.html#使用结构体更新语法从其他实例创建实例
[stack-only-data-copy]: ch04-01-what-is-ownership.html#只在栈上的数据拷贝
[ways-variables-and-data-interact-clone]: ch04-01-what-is-ownership.html#变量与数据交互的方式二克隆
[ways-variables-and-data-interact-clone]: ch04-01-what-is-ownership.html#使用克隆的变量与数据交互
[macros]: ch20-05-macros.html#宏

View File

@ -177,15 +177,29 @@ access the heap data." src="img/trpl04-04.svg" class="center" style="width:
#### 作用域与赋值
作用域、所有权和内存的关系反过来也是对的,它们也会被 `drop` 函数释放。当你给一个已有的变量赋一个全新的值时Rust 将会立即调用 `drop` 并释放原始值的内存。例如,考虑如下代码:
作用域、所有权和通过 `drop` 函数释放内存之间的关系反过来也同样成立。当你给一个已有的变量赋一个全新的值时Rust 将会立即调用 `drop` 并释放原始值的内存。例如,考虑如下代码:
```rust
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-04b-replacement-drop/src/main.rs:here}}
```
#### 变量与数据交互的方式(二):克隆
起初我们声明了变量 `s` 并绑定为一个 `"hello"` 值的 `String`。接着立即创建了一个值为 `"ahoy"``String` 并赋值给 `s`。在这里,完全没有任何内容指向了原始堆上的值。
如果我们 **确实** 需要深度复制 `String` 中堆上的数据,而不仅仅是栈上的数据,可以使用一个叫做 `clone` 的通用函数。第五章会讨论方法语法,不过因为方法在很多语言中是一个常见功能,所以之前你可能已经见过了。
<img alt="One table s representing the string value on the stack, pointing to
the second piece of string data (ahoy) on the heap, with the original string
data (hello) grayed out because it cannot be accessed anymore."
src="img/trpl04-05.svg"
class="center"
style="width: 50%;"
/>
<span class="caption">图 4-5: 当初始值被整体替换后的内存表现</span>
因此原始的字符串立刻就离开了作用域。Rust 会在其上运行 `drop` 函数同时内存会马上释放。当结尾打印其值时,将会是 `"ahoy, world!"`
#### 使用克隆的变量与数据交互
如果我们 **确实** 需要深度复制 `String` 中堆上的数据,而不仅仅是栈上的数据,可以使用一个叫做 `clone` 的常用方法。第五章会讨论方法语法,不过因为方法在很多语言中是一个常见功能,所以之前你可能已经见过了。
这是一个实际使用 `clone` 方法的例子:
@ -193,13 +207,13 @@ access the heap data." src="img/trpl04-04.svg" class="center" style="width:
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-05-clone/src/main.rs:here}}
```
这段代码能正常运行,并且明确产生图 4-3 中行为,这里堆上的数据 **确实** 被复制了。
这段代码能正常运行,并且明确产生图 4-3 中行为,这里堆上的数据**确实**被复制了。
当出现 `clone` 调用时,你知道一些特定的代码被执行而且这些代码可能相当消耗资源。你很容易察觉到一些不寻常的事情正在发生。
#### 只在栈上的数据:拷贝
这里还有一个没有提到的小窍门。这些代码使用了整型并且是有效的,它们是示例 4-2 中的一部分:
这里还有一个没有提到的细节。这些代码使用了整型并且是有效的,它们是示例 4-2 中的一部分:
```rust
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-06-copy/src/main.rs:here}}
@ -209,7 +223,7 @@ access the heap data." src="img/trpl04-04.svg" class="center" style="width:
原因是像整型这样的在编译时已知大小的类型被整个存储在栈上,所以拷贝其实际的值是快速的。这意味着没有理由在创建变量 `y` 后使 `x` 无效。换句话说,这里没有深浅拷贝的区别,所以这里调用 `clone` 并不会与通常的浅拷贝有什么不同,我们可以不用管它。
Rust 有一个叫做 `Copy` trait 的特殊注解,可以用在类似整型这样的存储在栈上的类型上([第十章][ch10]将会详细讲解 trait。如果一个类型实现了 `Copy` trait那么一个旧的变量在将其赋值给其他变量后仍然可用
Rust 有一个叫做 `Copy` trait 的特殊注解,可以用在类似整型这样的存储在栈上的类型上([第十章][ch10]将会详细讲解 trait。如果一个类型实现了 `Copy` trait那么一个旧的变量在将其赋值给其他变量后仍然有效
Rust 不允许自身或其任何部分实现了 `Drop` trait 的类型使用 `Copy` trait。如果我们对其值离开作用域时需要特殊处理的类型使用 `Copy` 注解,将会出现一个编译时错误。要学习如何为你的类型添加 `Copy` 注解以实现该 trait请阅读附录 C 中的 [“可派生的 trait”][derivable-traits]。
@ -247,7 +261,7 @@ Rust 不允许自身或其任何部分实现了 `Drop` trait 的类型使用 `Co
<span class="caption">示例 4-4: 转移返回值的所有权</span>
变量的所有权总是遵循相同的模式:将值赋给另一个变量时移动。当持有堆中数据值的变量离开作用域时,其值将通过 `drop` 被清理掉,除非数据被移动为另一个变量所有。
变量的所有权总是遵循相同的模式:将值赋给另一个变量时它会移动。当持有堆中数据值的变量离开作用域时,其值将通过 `drop` 被清理掉,除非数据被移动为另一个变量所有。
虽然这样是可以的,但是在每一个函数中都获取所有权并接着返回所有权有些啰嗦。如果我们想要函数使用一个值但不获取所有权该怎么办呢?如果我们还要接着使用它的话,每次都传进去再返回来就有点烦人了,除此之外,我们也可能想返回函数体中产生的一些数据。

View File

@ -1,11 +1,9 @@
## 引用与借用
> [ch04-02-references-and-borrowing.md](https://github.com/rust-lang/book/blob/main/src/ch04-02-references-and-borrowing.md)
> <br>
> commit 3d51f70c78162faaebcab0da0de2ddd333e7a8ed
<!-- https://github.com/rust-lang/book/blob/main/src/ch04-02-references-and-borrowing.md -->
<!-- commit b8b94b3d93ddd5186efa079913a78cb49a679a13 -->
示例 4-5 中的元组代码有这样一个问题:我们必须将 `String` 返回给调用函数,以便在调用 `calculate_length` 后仍能使用 `String`,因为 `String` 被移动到了 `calculate_length` 内。相反我们可以提供一个 `String` 值的引用reference。**引用***reference*)像一个指针,因为它是一个地址,我们可以由此访问储存于该地址的属于其他变量的数据。
与指针不同,引用确保指向某个特定类型的有效值。
示例 4-5 中的元组代码有这样一个问题:我们必须将 `String` 返回给调用函数,以便在调用 `calculate_length` 后仍能使用 `String`,因为 `String` 被移动到了 `calculate_length` 内。相反我们可以提供一个 `String` 值的引用reference。**引用***reference*)像一个指针,因为它是一个地址,我们可以由此访问储存于该地址的属于其他变量的数据。与指针不同,引用在其生命周期内保证指向某个特定类型的有效值。
下面是如何定义并使用一个(新的)`calculate_length` 函数,它以一个对象的引用作为参数而不是获取值的所有权:
@ -23,7 +21,7 @@ string data on the heap." src="img/trpl04-06.svg" class="center" />
<span class="caption">图 4-6`&String s` 指向 `String s1` 示意图</span>
> 注意:与使用 `&` 引用相反的操作是 **解引用***dereferencing*),它使用解引用运算符`*`。我们将会在第八章遇到一些解引用运算符,并在第十五章详细讨论解引用。
> 注意:与使用 `&` 引用相反的操作是 **解引用***dereferencing*),它使用解引用运算符 `*` 实现。我们将会在第八章遇到一些解引用运算符,并在第十五章详细讨论解引用。
仔细看看这个函数调用:
@ -31,7 +29,7 @@ string data on the heap." src="img/trpl04-06.svg" class="center" />
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-07-reference/src/main.rs:here}}
```
`&s1` 语法让我们创建一个 **指向** `s1` 的引用,但是并不拥有它。因为并不拥有这个值,所以当引用停止使用时,它所指向的值也不会被丢弃。
`&s1` 语法让我们创建一个**指向**值 `s1` 的引用,但是并不拥有它。因为并不拥有这个值,所以当引用停止使用时,它所指向的值也不会被丢弃。
同理,函数签名使用 `&` 来表明参数 `s` 的类型是一个引用。让我们增加一些解释性的注释:
@ -95,15 +93,15 @@ string data on the heap." src="img/trpl04-06.svg" class="center" />
* 至少有一个指针被用来写入数据。
* 没有同步数据访问的机制。
数据竞争会导致未定义行为难以在运行时追踪并且难以诊断和修复Rust 避免了这种情况的发生,因为它甚至不会编译存在数据竞争的代码
数据竞争会导致未定义行为难以在运行时追踪并且难以诊断和修复Rust 通过拒绝编译存在数据竞争的代码来避免此问题
一如既往,可以使用大括号来创建一个新的作用域,以允许拥有多个可变引用,只是不能 **同时** 拥有:
一如既往,可以使用大括号来创建一个新的作用域,以允许拥有多个可变引用,只是不能**同时**拥有:
```rust
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-11-muts-in-separate-scopes/src/main.rs:here}}
```
Rust 在同时使用可变与不可变引用时也采用类似的规则。这些代码会导致一个错误:
Rust 在同时使用可变与不可变引用时也强制采用类似的规则。这些代码会导致一个错误:
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-12-immutable-and-mutable-not-allowed/src/main.rs:here}}
@ -115,25 +113,25 @@ Rust 在同时使用可变与不可变引用时也采用的类似的规则。这
{{#include ../listings/ch04-understanding-ownership/no-listing-12-immutable-and-mutable-not-allowed/output.txt}}
```
哇哦!我们 **也** 不能在拥有不可变引用的同时拥有可变引用。
呼!我们**也**不能在拥有不可变引用的同时拥有可变引用。
不可变引用的借用者可不希望在借用时值会突然发生改变!然而,多个不可变引用是可以的,因为没有哪个只能读取数据的引用者能够影响其他引用者读取到的数据。
注意一个引用的作用域从声明的地方开始一直持续到最后一次使用为止。例如,因为最后一次使用不可变引用`println!`)发生在声明可变引用之前,所以如下代码是可以编译的:
注意一个引用的作用域从声明的地方开始一直持续到最后一次使用为止。例如,因为最后一次使用不可变引用的位置在 `println!`,它发生在声明可变引用之前,所以如下代码是可以编译的:
```rust,edition2021
```rust
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-13-reference-scope-ends/src/main.rs:here}}
```
不可变引用 `r1``r2` 的作用域在 `println!` 最后一次使用之后结束,这也是创建可变引用 `r3` 的地方。因为它们的作用域没有重叠,所以代码是可以编译的。编译器可以在作用域结束之前判断不再使用的引用。
不可变引用 `r1``r2` 的作用域在 `println!` 最后一次使用之后结束,这发生在可变引用 `r3` 被创建之前。因为它们的作用域没有重叠,所以代码是可以编译的。编译器可以在作用域结束之前判断不再使用的引用。
尽管这些错误有时使人沮丧,但请牢记这是 Rust 编译器在提前指出一个潜在的 bug在编译时而不是在运行时并精准显示问题所在。这样你就不必去跟踪为何数据并不是你想象中的那样。
尽管借用错误有时令人沮丧,但请牢记这是 Rust 编译器在提前指出一个潜在的 bug在编译时而不是在运行时并精准显示问题所在。这样你就不必去跟踪为何数据并不是你想象中的那样。
### 悬垂引用Dangling References
在具有指针的语言中,很容易通过释放内存时保留指向它的指针而错误地生成一个 **悬垂指针***dangling pointer*),所谓悬垂指针是其指向的内存可能已经被分配给其它持有者。相比之下,在 Rust 中编译器确保引用永远也不会变成悬垂状态:当你拥有一些数据的引用,编译器确保数据不会在其引用之前离开作用域。
在具有指针的语言中,很容易通过释放内存时保留指向它的指针而错误地生成一个**悬垂指针***dangling pointer*)—— 指向可能已被分配给其他用途的内存位置的指针。相比之下,在 Rust 中编译器确保引用永远也不会变成悬垂引用:当你拥有一些数据的引用,编译器确保数据不会在其引用之前离开作用域。
让我们尝试创建一个悬垂引用,Rust 会通过一个编译时错误来避免
让我们尝试创建一个悬垂引用,看看 Rust 如何通过通过一个编译时错误来防止它
<span class="filename">文件名src/main.rs</span>
@ -154,7 +152,7 @@ this function's return type contains a borrowed value, but there is no value
for it to be borrowed from
```
让我们仔细看看我们的 `dangle` 代码的每一步到底发生了什么:
让我们仔细看看我们的 `dangle` 代码的每个阶段到底发生了什么:
<span class="filename">文件名src/main.rs</span>
@ -176,7 +174,7 @@ for it to be borrowed from
让我们概括一下之前对引用的讨论:
* 在任意给定时间,**要么** 只能有一个可变引用,**要么** 只能有多个不可变引用。
* 在任意给定时间,**要么**只能有一个可变引用,**要么**只能有多个不可变引用。
* 引用必须总是有效的。
接下来我们来看看另一种不同类型的引用slice。

View File

@ -1,20 +1,21 @@
## Slice 类型
> [ch04-03-slices.md](https://github.com/rust-lang/book/blob/main/src/ch04-03-slices.md)
> <br>
> commit 3d51f70c78162faaebcab0da0de2ddd333e7a8ed
<!-- https://github.com/rust-lang/book/blob/main/src/ch04-03-slices.md -->
<!-- commit f8ed2ced5daaa26e2b3a69df4bf5e1ce04dda758 -->
*slice* 允许你引用集合中一段连续的元素序列而不用引用整个集合。slice 是一种引用,所以它有所有权。
**切片***slice*允许你引用集合中一段连续的元素序列而不用引用整个集合。slice 是一种引用,所以它不拥有所有权。
这里有一个编程小习题:编写一个函数,该函数接收一个用空格分隔单词的字符串,并返回在该字符串中找到的第一个单词。如果函数在该字符串中并未找到空格,则整个字符串就是一个单词,所以应该返回整个字符串。
> 注意:出于介绍字符串 slice 的目的,本小节假设只使用 ASCII 字符集;一个关于 UTF-8 处理的更全面的讨论位于第八章[“使用字符串储存 UTF-8 编码的文本”][strings]小节。
让我们推敲下如何不用 slice 编写这个函数的签名,来理解 slice 能解决的问题:
```rust,ignore
fn first_word(s: &String) -> ?
```
`first_word` 函数有一个参数 `&String`。因为我们不需要所有权,所以这没有问题。不过应该返回什么呢?我们并没有一个真正获取 **部分** 字符串的办法。不过,我们可以返回单词结尾的索引,结尾由一个空格表示。试试如示例 4-7 中的代码。
`first_word` 函数有一个参数 `&String`。因为我们不需要所有权,所以这没有问题。不过应该返回什么呢?我们并没有一个真正获取**部分**字符串的办法。不过,我们可以返回单词结尾的索引,结尾由一个空格表示。试试如示例 4-7 中的代码。
<span class="filename">文件名src/main.rs</span>
@ -40,7 +41,7 @@ fn first_word(s: &String) -> ?
因为 `enumerate` 方法返回一个元组,我们可以使用模式来解构,我们将在[第六章][ch6]中进一步讨论有关模式的问题。所以在 `for` 循环中,我们指定了一个模式,其中元组中的 `i` 是索引而元组中的 `&item` 是单个字节。因为我们从 `.iter().enumerate()` 中获取了集合元素的引用,所以模式中使用了 `&`
`for` 循环中,我们通过字节的字面值语法来寻找代表空格的字节。如果找到了一个空格,返回它的位置。否则,使用 `s.len()` 返回字符串的长度
`for` 循环中,我们通过字节的字面值语法来寻找代表空格的字节。如果找到了一个空格,返回它的位置。否则,使用 `s.len()` 返回字符串的长度
```rust,ignore
{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-07/src/main.rs:inside_for}}
@ -58,7 +59,7 @@ fn first_word(s: &String) -> ?
这个程序编译时没有任何错误,而且在调用 `s.clear()` 之后使用 `word` 也不会出错。因为 `word``s` 状态完全没有联系,所以 `word ` 仍然包含值 `5`。可以尝试用值 `5` 来提取变量 `s` 的第一个单词,不过这是有 bug 的,因为在我们将 `5` 保存到 `word` 之后 `s` 的内容已经改变。
我们不得不时刻担心 `word` 的索引与 `s` 中的数据不再同步,这很啰嗦且易出错!如果编写这么一个 `second_word` 函数的话,管理索引这件事将更加容易出问题。它的签名看起来像这样:
我们不得不时刻担心 `word` 的索引与 `s` 中的数据不再同步,这既繁琐又易出错!如果编写这么一个 `second_word` 函数的话,管理索引这件事将更加容易出问题。它的签名看起来像这样:
```rust,ignore
fn second_word(s: &String) -> (usize, usize) {

View File

@ -90,7 +90,7 @@
示例 5-7 中的代码也在 `user2` 中创建了一个新实例,但该实例中 `email` 字段的值与 `user1` 不同,而 `username``active``sign_in_count` 字段的值与 `user1` 相同。`..user1` 必须放在最后,以指定其余的字段应从 `user1` 的相应字段中获取其值,但我们可以选择以任何顺序为任意字段指定值,而不用考虑结构体定义中字段的顺序。
请注意,结构更新语法就像带有 `=` 的赋值,因为它移动了数据,就像我们在[“变量与数据交互的方式(一):移动”][move]部分讲到的一样。在这个例子中,总体上说我们在创建 `user2` 后就不能再使用 `user1` 了,因为 `user1``username` 字段中的 `String` 被移到 `user2` 中。如果我们给 `user2``email``username` 都赋予新的 `String` 值,从而只使用 `user1``active``sign_in_count` 值,那么 `user1` 在创建 `user2` 后仍然有效。`active` 和 `sign_in_count` 的类型是实现 `Copy` trait 的类型,所以我们在[“变量与数据交互的方式(二):克隆”][copy] 部分讨论的行为同样适用。
请注意,结构更新语法就像带有 `=` 的赋值,因为它移动了数据,就像我们在[“使用移动的变量与数据交互”][move]部分讲到的一样。在这个例子中,总体上说我们在创建 `user2` 后就不能再使用 `user1` 了,因为 `user1``username` 字段中的 `String` 被移到 `user2` 中。如果我们给 `user2``email``username` 都赋予新的 `String` 值,从而只使用 `user1``active``sign_in_count` 值,那么 `user1` 在创建 `user2` 后仍然有效。`active` 和 `sign_in_count` 的类型是实现 `Copy` trait 的类型,所以我们在[“使用克隆的变量与数据交互”][copy] 部分讨论的行为同样适用。
### 使用没有命名字段的元组结构体来创建不同的类型
@ -183,5 +183,5 @@
> 第十章会讲到如何修复这个问题以便在结构体中存储引用,不过现在,我们会使用像 `String` 这类拥有所有权的类型来替代 `&str` 这样的引用以修正这个错误。
[tuples]: ch03-02-data-types.html#元组类型
[move]: ch04-01-what-is-ownership.html#变量与数据交互的方式一移动
[copy]: ch04-01-what-is-ownership.html#变量与数据交互的方式二克隆
[move]: ch04-01-what-is-ownership.html#使用移动的变量与数据交互
[copy]: ch04-01-what-is-ownership.html#使用克隆的变量与数据交互