mirror of
https://github.com/KaiserY/trpl-zh-cn
synced 2024-11-09 00:43:59 +08:00
commit
75c4ec4b95
@ -209,7 +209,7 @@ fn main() {
|
||||
|
||||
数组在需要在栈(stack)而不是在堆(heap)上为数据分配空间时(第四章将讨论栈与堆的更多内容),或者是想要确保总是有固定数量的元素时十分有用。虽然它并不如 vector 类型那么灵活。vector 类型是标准库提供的一个 **允许** 增长和缩小长度的类似数组的集合类型。当不确定是应该使用数组还是 vector 的时候,你可能应该使用 vector:第八章会详细讨论 vector。
|
||||
|
||||
一个你可能想要使用数组而不是 vector 的例子是当程序需要知道一年中月份的名字时。程序不大可能回去增加或减少月份,这时你可以使用数组因为我们知道它总是含有 12 个元素:
|
||||
一个你可能想要使用数组而不是 vector 的例子是当程序需要知道一年中月份的名字时。程序不大可能会去增加或减少月份,这时你可以使用数组因为我们知道它总是含有 12 个元素:
|
||||
|
||||
```rust
|
||||
let months = ["January", "February", "March", "April", "May", "June", "July",
|
||||
|
@ -106,7 +106,7 @@ println!("{}", s); // This will print `hello, world!`
|
||||
1. 内存必须在运行时向操作系统请求。
|
||||
2. 需要一个当我们处理完 `String` 时将内存返回给操作系统的方法。
|
||||
|
||||
第一部分由我们完成:当调用 `String::from` 时,它的实现请求它需要的内存。这在编程语言中是非常通用的。
|
||||
第一部分由我们完成:当调用 `String::from` 时,它的实现 (*implementation*) 请求它需要的内存。这在编程语言中是非常通用的。
|
||||
|
||||
然而,第二部分实现起来就各有区别了。在有 **垃圾回收**(*garbage collector*,*GC*)的语言中, GC 记录并清除不再使用的内存,而我们作为程序员,并不需要关心他们。没有 GC 的话,识别出不再使用的内存并调用代码显式释放就是我们程序员的责任了,正如请求内存的时候一样。从历史的角度上说正确处理内存回收曾经是一个困难的编程问题。如果忘记回收了会浪费内存。如果过早回收了,将会出现无效变量。如果重复回收,这也是个 bug。我们需要 `allocate` 和 `free` 一一对应。
|
||||
|
||||
@ -220,7 +220,7 @@ println!("s1 = {}, s2 = {}", s1, s2);
|
||||
|
||||
这段代码能正常运行,也是如何显式产生图 4-5 中行为的方式,这里堆上的数据 **确实** 被复制了。
|
||||
|
||||
当出现 `clone` 调用时,你知道一些特有的代码被执行而且这些代码可能相当消耗资源。它作为一个代表发生了不同的行为的可视化的标识。
|
||||
当出现 `clone` 调用时,你知道一些特定的代码被执行而且这些代码可能相当消耗资源。你很容易察觉到一些不寻常的事情正在发生。
|
||||
|
||||
#### 只在栈上的数据:拷贝
|
||||
|
||||
@ -237,9 +237,9 @@ println!("x = {}, y = {}", x, y);
|
||||
|
||||
原因是像整型这样的在编译时已知大小的类型被整个储存在栈上,所以拷贝其实际的值是快速的。这意味着没有理由在创建变量 `y` 后使 `x` 无效。换句话说,这里没有深浅拷贝的区别,所以这里调用 `clone` 并不会与通常的浅拷贝有什么不同,我们可以不用管它。
|
||||
|
||||
Rust 有一个叫做 `Copy` trait 的特殊注解,可以用在类似整型这样的储存在栈上的类型(第十章详细讲解 trait)。如果一个类型拥有 `Copy` trait,一个旧的变量在(重新)赋值后仍然可用。Rust 不允许自身或其任何部分实现了 `Drop` trait 的类型使用`Copy` trait。如果我们对其值离开作用域时需要特殊处理的类型使用 `Copy` 注解,将会出现一个编译时错误。关于如何为你的类型增加 `Copy` 注解,请阅读附录 C 中的可导出 trait。
|
||||
Rust 有一个叫做 `Copy` trait 的特殊注解,可以用在类似整型这样的储存在栈上的类型(第十章详细讲解 trait)。如果一个类型拥有 `Copy` trait,一个旧的变量在将其赋值给其他变量后仍然可用。Rust 不允许自身或其任何部分实现了 `Drop` trait 的类型使用`Copy` trait。如果我们对其值离开作用域时需要特殊处理的类型使用 `Copy` 注解,将会出现一个编译时错误。关于如何为你的类型增加 `Copy` 注解,请阅读附录 C 中的可导出 trait。
|
||||
|
||||
那么什么类型是 `Copy` 的呢?可以查看给定类型的文档来确认,不过作为一个通用的规则,任何简单标量值的组合可以是 `Copy` 的,任何需要分配内存,或者本身就是某种形式资源的类型不会是 `Copy `的。如下是一些 `Copy` 的类型:
|
||||
那么什么类型是 `Copy` 的呢?可以查看给定类型的文档来确认,不过作为一个通用的规则,任何简单标量值的组合可以是 `Copy` 的,任何需要分配内存,或者本身就是某种形式资源的类型不会是 `Copy` 的。如下是一些 `Copy` 的类型:
|
||||
|
||||
* 所有整数类型,比如 `u32`。
|
||||
* 布尔类型,`bool`,它的值是 `true` 和 `false`。
|
||||
@ -248,7 +248,7 @@ Rust 有一个叫做 `Copy` trait 的特殊注解,可以用在类似整型这
|
||||
|
||||
### 所有权与函数
|
||||
|
||||
将值传递给函数在语义上与给变量赋值相似。向函数传递值可能会移动或者复制,就像赋值语句一样。示例 4-7 是一个带有变量何时进入和离开作用域标注的例子:
|
||||
将值传递给函数在语义上与给变量赋值相似。向函数传递值可能会移动或者复制,就像赋值语句一样。示例 4-7 是一个展示变量何时进入和离开作用域的例子:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -321,9 +321,9 @@ fn takes_and_gives_back(a_string: String) -> String { // a_string comes into
|
||||
|
||||
变量的所有权总是遵循相同的模式:将值赋值给另一个变量时移动它。当持有堆中数据值的变量离开作用域时,其值将通过 `drop` 被清理掉,除非数据被移动为另一个变量所有。
|
||||
|
||||
在每一个函数中都获取并接着返回所有权是冗余乏味的。如果我们想要函数使用一个值但不获取所有权改怎么办呢?如果我们还要接着使用它的话,每次都传递出去再传回来就有点烦人了,另外我们也可能想要返回函数体产生的任何(不止一个)数据。
|
||||
在每一个函数中都获取并接着返回所有权可能有些冗余。如果我们想要函数使用一个值但不获取所有权改怎么办呢?如果我们还要接着使用它的话,每次都传递出去再传回来就有点烦人了,另外我们也可能想要返回函数体产生的任何(不止一个)数据。
|
||||
|
||||
使用元组来返回多个值是可能的,像这样:
|
||||
可以使用元组来返回多个值,像这样:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -343,4 +343,4 @@ fn calculate_length(s: String) -> (String, usize) {
|
||||
}
|
||||
```
|
||||
|
||||
但是这不免有些形式主义,同时这离一个通用的观点还有很长距离。幸运的是,Rust 对此提供了一个功能,叫做 **引用**(*references*)。
|
||||
但是这不免有些形式主义,而且这种场景应该很常见。幸运的是,Rust 对此提供了一个功能,叫做 **引用**(*references*)。
|
||||
|
@ -4,7 +4,7 @@
|
||||
> <br>
|
||||
> commit d06a6a181fd61704cbf7feb55bc61d518c6469f9
|
||||
|
||||
在上一部分的结尾处的使用元组的代码是有问题的,我们需要将 `String` 返回给调用者函数这样就可以在调用 `calculate_length` 后仍然可以使用 `String` 了,因为 `String` 先被移动到了 `calculate_length`。
|
||||
有这样一个问题:在上一部分的结尾处的使用元组的代码中,因为 `String` 先被移动到了 `calculate_length` 内,因此我们不得不将 `String` 作为返回值,这样在调用 `calculate_length` 后才仍然可以使用 `String`。
|
||||
|
||||
下面是如何定义并使用一个(新的)`calculate_length` 函数,它以一个对象的 **引用** 作为参数而不是获取值的所有权:
|
||||
|
||||
@ -184,7 +184,7 @@ immutable
|
||||
|
||||
哇哦!我们 **也** 不能在拥有不可变引用的同时拥有可变引用。不可变引用的用户可不希望在它的眼皮底下值突然就被改变了!然而,多个不可变引用是没有问题的因为没有哪个只能读取数据的人有能力影响其他人读取到的数据。
|
||||
|
||||
即使这些错误有时是使人沮丧的。记住这是 Rust 编译器在提早指出一个潜在的 bug(在编译时而不是运行时)并明确告诉你问题在哪,而不是任由你去追踪为何有时数据并不是你想象中的那样。
|
||||
尽管这些错误有时使人沮丧,但请牢记这是 Rust 编译器在提早指出一个潜在的 bug(在编译时而不是运行时)并明确告诉你问题在哪,而不是任由你去追踪为何有时数据并不是你想象中的那样。
|
||||
|
||||
### 悬垂引用
|
||||
|
||||
|
@ -93,7 +93,7 @@ fn main() {
|
||||
|
||||
这个程序编译时没有任何错误,而且在调用 `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) {
|
||||
|
@ -226,4 +226,4 @@ let origin = Point(0, 0, 0);
|
||||
> | ^ expected lifetime parameter
|
||||
> ```
|
||||
>
|
||||
> 第十章会讲到如何修复这个问题以便在结构体中储存引用,不过现在,通过从像 `&str` 这样的引用切换到像 `String` 这类拥有所有权的类型来修改修改这个错误。
|
||||
> 第十章会讲到如何修复这个问题以便在结构体中储存引用,不过现在,我们会使用像 `String` 这类拥有所有权的类型来替代 `&str` 这样的引用以修正这个错误。
|
||||
|
@ -189,6 +189,6 @@ rect1 is Rectangle {
|
||||
}
|
||||
```
|
||||
|
||||
Rust 为我们提供了很多可以通过 `derive` 注解来使用的 trait,他们可以为我们的自定义类型增加有益的行为。这些 trait 和行为在附录 C 中列出。第十章会涉及到如何通过自定义行为来实现这些 trait,同时还有如何创建你自己的 trait。
|
||||
Rust 为我们提供了很多可以通过 `derive` 注解来使用的 trait,他们可以为我们的自定义类型增加实用的行为。这些 trait 和行为在附录 C 中列出。第十章会涉及到如何通过自定义行为来实现这些 trait,同时还有如何创建你自己的 trait。
|
||||
|
||||
我们的 `area` 函数是非常特化的————它只是计算了长方形的面积。如果这个行为与 `Rectangle` 结构体再结合得更紧密一些就更好了,因为它不能用于其他类型。现在让我们看看如何继续重构这些代码,来将 `area` 函数协调进 `Rectangle` 类型定义的`area` **方法** 中。
|
||||
|
@ -41,7 +41,7 @@ fn main() {
|
||||
|
||||
在 `area` 的签名中,开始使用 `&self` 来替代 `rectangle: &Rectangle`,因为该方法位于 `impl Rectangle` 上下文中所以 Rust 知道 `self` 的类型是 `Rectangle`。注意仍然需要在 `self` 前面加上 `&`,就像 `&Rectangle` 一样。方法可以选择获取 `self` 的所有权,像我们这里一样不可变的借用 `self`,或者可变的借用 `self`,就跟其他别的参数一样。
|
||||
|
||||
这里选择 `&self` 跟在函数版本中使用 `&Rectangle` 出于同样的理由:我们并不想获取所有权,只希望能够读取结构体中的数据,而不是写入。如果想要能够在方法中改变调用方法的实例的话,需要将第一个参数改为 `&mut self`。通过仅仅使用 `self` 作为第一个参数来使方法获取实例的所有权,不过这是很少见的;这种技术通常用在当方法将 `self` 转换成别的实例的时候,这时我们想要防止调用者在转换之后使用原始的实例。
|
||||
这里选择 `&self` 跟在函数版本中使用 `&Rectangle` 出于同样的理由:我们并不想获取所有权,只希望能够读取结构体中的数据,而不是写入。如果想要能够在方法中改变调用方法的实例的话,需要将第一个参数改为 `&mut self`。很少见到通过仅仅使用 `self` 作为第一个参数来使方法获取实例的所有权;这种技术通常用在当方法将 `self` 转换成别的实例的时候,这时我们想要防止调用者在转换之后使用原始的实例。
|
||||
|
||||
使用方法而不是函数,除了使用了方法语法和不需要在每个函数签名中重复` self` 类型之外,其主要好处在于组织性。我将某个类型实例能做的所有事情都一起放入 `impl` 块中,而不是让将来的用户在我们的代码中到处寻找 `Rectangle` 的功能。
|
||||
|
||||
@ -182,4 +182,4 @@ impl Rectangle {
|
||||
|
||||
结构体让我们可以在自己的范围内创建有意义的自定义类型。通过结构体,我们可以将相关联的数据片段联系起来并命名他们来使得代码更清晰。方法允许为结构体实例指定行为,而关联函数将特定功能置于结构体的命名空间中并且无需一个实例。
|
||||
|
||||
结构体并不是创建自定义类型的唯一方法;让我们转向 Rust 的枚举功能并为自己的工具箱再填一个工具。
|
||||
结构体并不是创建自定义类型的唯一方法;让我们转向 Rust 的枚举功能并为自己的工具箱再添一个工具。
|
||||
|
@ -4,7 +4,7 @@
|
||||
> <br>
|
||||
> commit d06a6a181fd61704cbf7feb55bc61d518c6469f9
|
||||
|
||||
让我们通过一用代码来表现的场景,来看看为什么这里枚举是有用的而且比结构体更合适。比如我们要处理 IP 地址。目前被广泛使用的两个主要 IP 标准:IPv4(version four)和 IPv6(version six)。这是我们的程序只可能会遇到两种 IP 地址:所以可以 **枚举** 出所有可能的值,这也正是它名字的由来。
|
||||
让我们通过一用代码来表现的场景,来看看为什么这里枚举是有用的而且比结构体更合适。比如我们要处理 IP 地址。目前被广泛使用的两个主要 IP 标准:IPv4(version four)和 IPv6(version six)。这是我们的程序可能会遇到的 IP 地址的所有可能性:所以可以 **枚举** 出所有可能的值,这也正是它名字的由来。
|
||||
|
||||
任何一个 IP 地址要么是 IPv4 的要么是 IPv6 的而不能两者都是。IP 地址的这个特性使得枚举数据结构非常适合这个场景,因为枚举值只可能是其中一个成员。IPv4 和 IPv6 从根本上讲仍是 IP 地址,所以当代码在处理申请任何类型的 IP 地址的场景时应该把他们当作相同的类型。
|
||||
|
||||
@ -212,7 +212,7 @@ m.call();
|
||||
> crashes, which have probably caused a billion dollars of pain and damage in
|
||||
> the last forty years.
|
||||
>
|
||||
> 我称之为我万亿美元的错误。当时,我在为一个面向对象语言设计第一个综合性的面向引用的类型系统。我的目标是通过编译器的自动检查来保证所有引用的应有都应该是绝对安全的。不过我未能抗拒引入一个空引用的诱惑,仅仅是因为它是这么的容易实现。这引发了无数错误、漏洞和系统崩溃,在之后的四十多年中造成了数以万计美元的苦痛和伤害。
|
||||
> 我称之为我万亿美元的错误。当时,我在为一个面向对象语言设计第一个综合性的面向引用的类型系统。我的目标是通过编译器的自动检查来保证所有引用的使用都应该是绝对安全的。不过我未能抗拒引入一个空引用的诱惑,仅仅是因为它是这么的容易实现。这引发了无数错误、漏洞和系统崩溃,在之后的四十多年中造成了数以万计美元的苦痛和伤害。
|
||||
|
||||
空值的问题在于当你尝试像一个非空值那样使用一个空值,会出现某种形式的错误。因为空和非空的属性是无处不在的,非常容易出现这类错误。
|
||||
|
||||
@ -240,12 +240,12 @@ let some_string = Some("a string");
|
||||
let absent_number: Option<i32> = None;
|
||||
```
|
||||
|
||||
如果使用 `None` 而不是 `Some`,需要告诉 Rust `Option<T>` 是什么类型的,因为编译器只通过 `None` 值无法推断出 `Some` 成员的类型。
|
||||
如果使用 `None` 而不是 `Some`,需要告诉 Rust `Option<T>` 是什么类型的,因为编译器只通过 `None` 值无法推断出 `Some` 变量保留的值的类型。
|
||||
|
||||
当有一个 `Some` 值时,我们就知道存在一个值,而这个值保存在 `Some` 中。当有个`None` 值时,在某种意义上它跟空值是相同的意义:并没有一个有效的值。那么,`Option<T>` 为什么就比空值要好呢?
|
||||
|
||||
|
||||
简而言之,因为 `Option<T>` 和 `T`(这里 `T` 可以是任何类型)是不同的类型,编译器不允许像一个被定义的有效的类型那样使用 `Option<T>`。例如,这些代码不能编译,因为它尝试将 `Option<i8>` 与 `i8` 相比:
|
||||
简而言之,因为 `Option<T>` 和 `T`(这里 `T` 可以是任何类型)是不同的类型,编译器不允许像一个被定义的有效的类型那样使用 `Option<T>`。例如,这些代码不能编译,因为它尝试将 `Option<i8>` 与 `i8` 相加:
|
||||
|
||||
```rust,ignore
|
||||
let x: i8 = 5;
|
||||
|
Loading…
Reference in New Issue
Block a user