pull/501/head
sunface 3 years ago
commit 5005427c9e

@ -1,4 +1,4 @@
## 附录 D可派生的 trait
## 附录 D派生特征 trait
在本书的各个部分中,我们讨论了可应用于结构体和枚举定义的 `derive` 属性。被 `derive` 标记的对象会自动实现对应的默认特征代码,继承相应的功能。
@ -54,19 +54,19 @@
### 复制值的 `Clone``Copy`
`Clone` 特征用于创建一个值的深拷贝deep copy复制过程可能包含代码的执行以及堆上数据的复制。查阅 [“通过Clone来进行深拷贝”](../core/ownership.md#通过Clone来进行深拷贝)获取有关 `Clone` 的更多信息。
`Clone` 特征用于创建一个值的深拷贝deep copy复制过程可能包含代码的执行以及堆上数据的复制。查阅 [通过 Clone 进行深拷贝](https://course.rs/basic/ownership/ownership.html#克隆深拷贝)获取有关 `Clone` 的更多信息。
派生 `Clone` 实现了 `clone` 方法,当为整个的类型实现 `Clone` 时,在该类型的每一部分上都会调用 `clone` 方法。这意味着类型中所有字段或值也必须实现了 `Clone`,这样才能够派生 `Clone`
例如当在一个切片slice上调用 `to_vec` 方法时, `Clone` 是必须的。切片只是一个引用,并不拥有其所包含的实例数据,但是从 `to_vec` 中返回的Vector需要拥有实例数据因此 `to_vec` 需要在每个元素上调用 `clone` 来逐个复制。因此,存储在切片中的类型必须实现 `Clone`
`Copy` 特征允许你通过只拷贝存储在栈上的数据来复制值(浅拷贝),而无需复制存储在堆上的底层数据。查阅第四章[通过Copy复制栈数据](../core/ownership.md#通过Copy复制栈数据) 的部分来获取有关 `Copy` 的更多信息。
`Copy` 特征允许你通过只拷贝存储在栈上的数据来复制值(浅拷贝),而无需复制存储在堆上的底层数据。查阅 [通过 Copy 复制栈数据](https://course.rs/basic/ownership/ownership.html#拷贝浅拷贝) 的部分来获取有关 `Copy` 的更多信息。
实际上 `Copy` 特征并不阻止你在实现时使用了深拷贝,只是,我们不应该这么做,毕竟遵循一个语言的惯例是很重要的。当用户看到 `Copy` 时,潜意识就应该知道这是浅拷贝,复制一个值会非常快。
当一个类型的内部字段全部实现了 `Copy` 时,你就可以在该类型上派上 `Copy` 特征。 一个类型如果要实现 `Copy` 它必须先实现 `Clone` ,因为一个类型实现 `Clone` 后,就等于顺便实现了 `Copy`
总之, `Copy` 拥有更好的性能,当浅拷贝足够的时候,就不要使用 `Clone` ,不然会导致你的代码运行更慢,对于[性能优化](../performance/intro.md)来说,一个很大的方面就是减少热点路径深拷贝的发生。
总之, `Copy` 拥有更好的性能,当浅拷贝足够的时候,就不要使用 `Clone` ,不然会导致你的代码运行更慢,对于[性能优化](https://course.rs/profiling/performance/intro.html)来说,一个很大的方面就是减少热点路径深拷贝的发生。
### 固定大小的值映射的 `Hash`

@ -4,7 +4,7 @@
## 字符类型(char)
字符,对于没有其它编程经验的新手来说可能不太好理解(没有编程经验敢来学 Rust 的绝对是好汉),但是你可以把它理解为英文中的字母,中文中的汉字。
字符,对于没有其它编程经验的新手来说可能不太好理解(没有编程经验敢来学 Rust 的绝对是好汉),但是你可以把它理解为英文中的字母,中文中的汉字。
下面的代码展示了几个颇具异域风情的字符:
```
@ -39,7 +39,7 @@ $ cargo run
## 布尔(bool)
Rust 中的布尔类型有两个可能的值:`true` 和 `false`, 布尔值占用内存的大小为 `1` 个字节:
Rust 中的布尔类型有两个可能的值:`true` 和 `false`布尔值占用内存的大小为 `1` 个字节:
```rust
fn main() {

@ -12,7 +12,7 @@ Rust 每个值都有其确切的数据类型,总的来说可以分为两类:
## 类型推导与标注
`Python` `Javascript` 等动态语言不同Rust 是一门静态类型语言,也就是编译器必须在编译期知道我们所有变量的类型,但这不意味着你需要为每个变量指定类型,因为**Rust 编译器很聪明,它可以根据变量的值和上下文中的使用方式来自动推导出变量的类型**,同时编译器也不够聪明,在某些情况下,它无法推导出变量类型,需要手动去给予一个类型标注,关于这一点在[Rust语言初印象](../../first-try/hello-world.md#Rust语言初印象)中有过展示。
与 Python、Javascript 等动态语言不同Rust 是一门静态类型语言,也就是编译器必须在编译期知道我们所有变量的类型,但这不意味着你需要为每个变量指定类型,因为 **Rust 编译器很聪明,它可以根据变量的值和上下文中的使用方式来自动推导出变量的类型**,同时编译器也不够聪明,在某些情况下,它无法推导出变量类型,需要手动去给予一个类型标注,关于这一点在 [Rust 语言初印象](https://course.rs/first-try/hello-world.html#rust-语言初印象)中有过展示。
来看段代码:
```rust

@ -7,7 +7,7 @@
Rust 使用一个相对传统的语法来创建整数(`1``2`...)和浮点数(`1.0``1.1`...)。整数、浮点数的运算和你在其它语言上见过的一致,都是通过常见的运算符来完成。
> 不仅仅是数值类型Rust 也允许在复杂类型上定义运算符,例如在自定义类型上定义 `+` 运算符这种行为被称为运算符重载Rust 具体支持的可重载运算符见[这里](../../appendix/operators.md#运算符)
> 不仅仅是数值类型Rust 也允许在复杂类型上定义运算符,例如在自定义类型上定义 `+` 运算符这种行为被称为运算符重载Rust 具体支持的可重载运算符见[附录 B](https://course.rs/appendix/operators.html#运算符)
#### 整数类型
@ -97,7 +97,7 @@ fn main() {
}
```
这些语句中的每个表达式都使用了数学运算符,并且计算结果为一个值,然后绑定到一个变量上。[附录](../../appendix/operators.md)中给出了 Rust 提供的所有运算符的列表。
这些语句中的每个表达式都使用了数学运算符,并且计算结果为一个值,然后绑定到一个变量上。[附录 B](https://course.rs/appendix/operators.html#运算符) 中给出了 Rust 提供的所有运算符的列表。
再来看一个综合性的示例:

@ -29,7 +29,7 @@ fn main() {
}
```
接下来我们的学习非常类似原型设计:有的方法只提供 API 接口但是不提供具体实现。此外有的变量在声明之后并未使用因此在这个阶段我们需要排除一些编译器噪音Rust 在编译的时候会扫描代码,变量声明后未使用会以 `warning` 警告的形式进行提示),引入 `#![allow(unused_variables)]` 属性标记,该标记会告诉编译器忽略未使用的变量,不要抛出 `warning` 警告,具体的常见编译器属性你可以在这里查阅:[编译器属性标记](../../profiling/compiler/attributes.md).
接下来我们的学习非常类似原型设计:有的方法只提供 API 接口但是不提供具体实现。此外有的变量在声明之后并未使用因此在这个阶段我们需要排除一些编译器噪音Rust 在编译的时候会扫描代码,变量声明后未使用会以 `warning` 警告的形式进行提示),引入 `#![allow(unused_variables)]` 属性标记,该标记会告诉编译器忽略未使用的变量,不要抛出 `warning` 警告,具体的常见编译器属性你可以在这里查阅:[编译器属性标记](https://course.rs/profiling/compiler/attributes.html)。
`read` 函数也非常有趣,它返回一个 `!` 类型,这个表明该函数是一个发散函数,不会返回任何值,包括 `()`。`unimplemented!()` 告诉编译器该函数尚未实现,`unimplemented!()` 标记通常意味着我们期望快速完成主要代码,回头再通过搜索这些标记来完成次要代码,类似的标记还有 `todo!()`,当代码执行到这种未实现的地方时,程序会直接报错。你可以反注释 `read(&mut f1, &mut vec![]);` 这行,然后再观察下结果。

@ -53,7 +53,7 @@ let world = &s[6..11];
<img alt="" src="https://pic1.zhimg.com/80/v2-69da917741b2c610732d8526a9cc86f5_1440w.jpg" class="center" style="width: 50%;" />
在使用 Rust 的 `..` [range序列](../base-type/numbers.md#序列(Range))语法时,如果你想从索引 0 开始,可以使用如下的方式,这两个是等效的:
在使用 Rust 的 `..` [range序列](https://course.rs/base-type/numbers.html#序列range)语法时,如果你想从索引 0 开始,可以使用如下的方式,这两个是等效的:
```rust
let s = String::from("hello");
@ -81,14 +81,14 @@ let slice = &s[0..len];
let slice = &s[..];
```
>在对字符串使用切片语法时需要格外小心切片的索引必须落在字符之间的边界位置也就是UTF8字符的边界例如中文在UTF8中占用三个字节,下面的代码就会崩溃:
>在对字符串使用切片语法时需要格外小心切片的索引必须落在字符之间的边界位置也就是UTF-8字符的边界例如中文在UTF-8中占用三个字节,下面的代码就会崩溃:
>```rust
> let s = "中国人";
> let a = &s[0..2];
> println!("{}",a);
>```
>因为我们只取 `s` 字符串的前两个字节,但是本例中每个汉字占用三个字节,因此没有落在边界处,也就是连 `中` 字都取不完整,此时程序会直接崩溃退出,如果改成 `&s[0..3]`,则可以正常通过编译。
> 因此,当你需要对字符串做切片索引操作时,需要格外小心这一点, 关于该如何操作 UTF8 字符串,参见[这里](#操作UTF8字符串)
> 因此,当你需要对字符串做切片索引操作时,需要格外小心这一点, 关于该如何操作 UTF-8 字符串,参见[这里](#操作-UTF8-字符串)
字符串切片的类型标识是 `&str`,因此我们可以这样声明一个函数,输入 `String` 类型,返回它的切片: `fn first_word(s: &String) -> &str `
@ -155,11 +155,11 @@ let s: &str = "Hello, world!";
## 什么是字符串?
顾名思义,字符串是由字符组成的连续集合,但是在上一节中我们提到过,**Rust 中的字符是 Unicode 类型,因此每个字符占据 4 个字节内存空间,但是在字符串中不一样,字符串是 UTF8 编码,也就是字符串中的字符所占的字节数是变化的(1 - 4)**,这样有助于大幅降低字符串所占用的内存空间。
顾名思义,字符串是由字符组成的连续集合,但是在上一节中我们提到过,**Rust 中的字符是 Unicode 类型,因此每个字符占据 4 个字节内存空间,但是在字符串中不一样,字符串是 UTF-8 编码,也就是字符串中的字符所占的字节数是变化的(1 - 4)**,这样有助于大幅降低字符串所占用的内存空间。
Rust 在语言级别,只有一种字符串类型: `str`,它通常是以引用类型出现 `&str`,也就是上文提到的字符串切片。虽然语言级别只有上述的 `str` 类型,但是在标准库里,还有多种不同用途的字符串类型,其中使用最广的即是 `String` 类型。
`str` 类型是硬编码进可执行文件,也无法被修改,但是 `String` 则是一个可增长、可改变且具有所有权的 UTF8 编码字符串,**当 Rust 用户提到字符串时,往往指的就是 `String` 类型和 `&str` 字符串切片类型,这两个类型都是 UTF8 编码**。
`str` 类型是硬编码进可执行文件,也无法被修改,但是 `String` 则是一个可增长、可改变且具有所有权的 UTF-8 编码字符串,**当 Rust 用户提到字符串时,往往指的就是 `String` 类型和 `&str` 字符串切片类型,这两个类型都是 UTF-8 编码**。
除了 `String` 类型的字符串Rust 的标准库还提供了其他类型的字符串,例如 `OsString` `OsStr` `CsString` 和` CsStr` 等,注意到这些名字都以 `String` 或者 `Str` 结尾了吗?它们分别对应的是具有所有权和被借用的变量。
@ -185,7 +185,7 @@ fn main() {
assert_eq!(s,"hello,world!");
// 从现有的&str切片创建String类型
// String与&str都是UTF8编码因此支持中文
// String与&str都是UTF-8编码因此支持中文
let mut s = String::from("你好,世界");
// 将字符'!'推入s中
s.push('!');
@ -243,7 +243,7 @@ fn say_hello(s: &str) {
}
```
实际上这种灵活用法是因为 `deref` 隐式强制转换,具体我们会在 [Deref特征](../../traits/deref.md)进行详细讲解。
实际上这种灵活用法是因为 `deref` 隐式强制转换,具体我们会在 [`Deref` 特征](https://course.rs/advance/smart-pointer/deref.html)进行详细讲解。
## 字符串索引
@ -262,11 +262,11 @@ fn say_hello(s: &str) {
```
#### 深入字符串内部
字符串的底层的数据存储格式实际上是[ `u8` ],一个字节数组。对于 `let hello = String::from("Hola");` 这行代码来说, `hello` 的长度是 `4` 个字节,因为 `"hola"` 中的每个字母在 UTF8 编码中仅占用 1 个字节,但是对于下面的代码呢?
字符串的底层的数据存储格式实际上是[ `u8` ],一个字节数组。对于 `let hello = String::from("Hola");` 这行代码来说, `hello` 的长度是 `4` 个字节,因为 `"hola"` 中的每个字母在 UTF-8 编码中仅占用 1 个字节,但是对于下面的代码呢?
```rust
let hello = String::from("中国人");
```
如果问你该字符串多长,你可能会说 `3`,但是实际上是 `9` 个字节的长度,因为大部分常用汉字在 UTF8 中的长度是 `3` 个字节,因此这种情况下对 `hello` 进行索引,访问 `&hello[0]` 没有任何意义,因为你取不到 `中` 这个字符,而是取到了这个字符三个字节中的第一个字节,这是一个非常奇怪而且难以理解的返回值。
如果问你该字符串多长,你可能会说 `3`,但是实际上是 `9` 个字节的长度,因为大部分常用汉字在 UTF-8 中的长度是 `3` 个字节,因此这种情况下对 `hello` 进行索引,访问 `&hello[0]` 没有任何意义,因为你取不到 `中` 这个字符,而是取到了这个字符三个字节中的第一个字节,这是一个非常奇怪而且难以理解的返回值。
#### 字符串的不同表现形式
现在看一下用梵文写的字符串 `“नमस्ते”`, 它底层的字节数组如下形式:
@ -288,7 +288,7 @@ let hello = String::from("中国人");
还有一个原因导致了 Rust 不允许去索引字符串:因为索引操作,我们总是期望它的性能表现是 O(1),然而对于 `String` 类型来说,无法保证这一点,因为 Rust 可能需要从 0 开始去遍历字符串来定位合法的字符。
## 字符串切片
前文提到过,字符串切片是非常危险的操作,因为切片的索引是通过字节来进行,但是字符串又是 UTF8 编码,因此你无法保证索引的字节刚好落在字符的边界上,例如:
前文提到过,字符串切片是非常危险的操作,因为切片的索引是通过字节来进行,但是字符串又是 UTF-8 编码,因此你无法保证索引的字节刚好落在字符的边界上,例如:
```rust
let hello = "中国人";
@ -304,7 +304,7 @@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
因此在通过索引区间来访问字符串时,**需要格外的小心**,一不注意,就会导致你程序的崩溃!
## 操作 UTF8 字符串
前文提到了几种使用 UTF8 字符串的方式,下面来一一说明。
前文提到了几种使用 UTF-8 字符串的方式,下面来一一说明。
#### 字符
如果你想要以 Unicode 字符的方式遍历字符串,最好的办法是使用 `chars` 方法,例如:
@ -341,7 +341,7 @@ for b in "中国人".bytes() {
```
#### 获取子串
想要准确的从UTF8字符串中获取子串是较为复杂的事情例如想要从 `holla中国人नमस्ते` 这种变长的字符串中取出某一个子串,使用标准库你是做不到的。
想要准确的从UTF-8字符串中获取子串是较为复杂的事情例如想要从 `holla中国人नमस्ते` 这种变长的字符串中取出某一个子串,使用标准库你是做不到的。
你需要在 `crates.io` 上搜索 `utf8` 来寻找想要的功能。
可以考虑尝试下这个库:[utf8_slice](https://crates.io/crates/utf8_slice)。
@ -355,6 +355,7 @@ for b in "中国人".bytes() {
就字符串字面值来说,我们在编译时就知道其内容,最终字面值文本被直接硬编码进可执行文件中,这使得字符串字面值快速且高效,这主要得益于字符串字面值的不可变性。不幸的是,我们不能为了获得这种性能,而把每一个在编译时大小未知的文本都放进内存中(你也做不到!),因为有的字符串是在程序运行得过程中动态生成的。
对于 `String` 类型,为了支持一个可变、可增长的文本片段,需要在堆上分配一块在编译时未知大小的内存来存放内容,这些都是在程序运行时完成的:
- 首先向操作系统请求内存来存放 `String` 对象
- 在使用完成后,将内存释放,归还给操作系统

@ -9,6 +9,7 @@
#### 定义结构体
一个结构体有几部分组成:
- 通过关键字 `struct` 定义
- 一个清晰明确的结构体 `名称`
- 几个有名字的结构体 `字段`
@ -105,7 +106,7 @@ fn build_user(email: String, username: String) -> User {
>
> 聪明的读者肯定要发问了:明明有三个字段进行了自动赋值,为何只有 `username` 发生了所有权转移?
>
> 仔细回想一下[所有权](../ownership/ownership.md#拷贝(浅拷贝))那一节的内容,我们提到了 `Copy` 特征:实现了 `Copy` 特征的类型无需所有权转移,可以直接在赋值时进行
> 仔细回想一下[所有权](../ownership/ownership.md#拷贝浅拷贝)那一节的内容,我们提到了 `Copy` 特征:实现了 `Copy` 特征的类型无需所有权转移,可以直接在赋值时进行
> 数据拷贝,其中 `bool``u64` 类型就实现了 `Copy` 特征,因此 `active``sign_in_count` 字段在赋值给 `user2` 时,仅仅发生了拷贝,而不是所有权转移。
>
> 值得注意的是:`username` 所有权被转移给了 `user2`,导致了 `user1` 无法再被使用,但是并不代表 `user1` 内部的其它字段不能被继续使用,例如:
@ -201,7 +202,7 @@ impl SomeTrait for AlwaysEqual {
在之前的 `User` 结构体的定义中,有一处细节:我们使用了自身拥有所有权的 `String` 类型而不是基于引用的 `&str` 字符串切片类型。这是一个有意而为之的选择:因为我们想要这个结构体拥有它所有的数据,而不是从其它地方借用数据。
你也可以让 `User` 结构体从其它对象借用数据,不过这么做,就需要引入[生命周期lifetimes](../../advance/lifetime/basic.md)这个新概念(也是一个复杂的概念),简而言之,生命周期能确保结构体的作用范围要比它所借用的数据的作用范围要小。
你也可以让 `User` 结构体从其它对象借用数据,不过这么做,就需要引入[生命周期(lifetimes)](../../advance/lifetime/basic.md)这个新概念(也是一个复杂的概念),简而言之,生命周期能确保结构体的作用范围要比它所借用的数据的作用范围要小。
总之,如果你想在结构体中使用一个引用,就必须加上生命周期,否则就会报错:
@ -273,7 +274,7 @@ fn main() {
}
```
首先可以观察到,上面使用了 `{}` 而不是之前的 `{:}`,运行后报错:
首先可以观察到,上面使用了 `{}` 而不是之前的 `{:?}`,运行后报错:
```shell
error[E0277]: `Rectangle` doesn't implement `std::fmt::Display`
```
@ -319,7 +320,7 @@ error[E0277]: `Rectangle` doesn't implement `Debug`
- 手动实现
- 使用 `derive` 派生实现
后者简单的多,但是也有限制,具体见[附录](https://course.rs/appendix/derive.html),这里我们就不再深入讲解,来看看该如何使用:
后者简单的多,但是也有限制,具体见[附录 D](https://course.rs/appendix/derive.html),这里我们就不再深入讲解,来看看该如何使用:
```rust
#[derive(Debug)]
struct Rectangle {
@ -355,9 +356,9 @@ rect1 is Rectangle {
此时结构体的输出跟我们创建时候的代码几乎一模一样了!当然,如果大家还是不满足,那最好还是自己实现 `Display` 特征,以向用户更美的展示你的私藏结构体。关于格式化输出的更多内容,我们强烈推荐看看这个[章节](https://course.rs/basic/formatted-output.html#debug-特征)。
还有一个简单的输出 debug 信息的方法,那就是使用 [`dbg!` 宏](https://doc.rust-lang.org/std/macro.dbg.html),它会拿走表达式的所有权,然后打出相应的文件名、行号等 debug 信息,当然还有我们需要的表达式的求值结果。**除之外,它最终还会把表达式值的所有权返回!**
还有一个简单的输出 debug 信息的方法,那就是使用 [`dbg!` 宏](https://doc.rust-lang.org/std/macro.dbg.html),它会拿走表达式的所有权,然后打出相应的文件名、行号等 debug 信息,当然还有我们需要的表达式的求值结果。**除之外,它最终还会把表达式值的所有权返回!**
> `dbg!` 输出到的是标准错误输出 `stderr`,而 `println!` 输出到标准输出 `stdout`
> `dbg!` 输出到标准错误输出 `stderr`,而 `println!` 输出到标准输出 `stdout`
下面的例子中清晰的展示了 `dbg!` 如何在打印出信息的同时,还把表达式的值赋给了 `width`:
```rust

@ -2,7 +2,7 @@
上节中提到,如果仅仅支持通过转移所有权的方式获取一个值,那会让程序变得复杂。 Rust 能否像其它编程语言一样,使用某个变量的指针或者引用呢?答案是可以。
Rust 通过 `借用(Borrowing)` 这个概念来达成上述的目的: **获取变量的引用,称之为借用(borrowing)**。正如现实生活中,如果一个人拥有某样东西,你可以从他那里借来,当使用完毕后,也必须要物归原主。
Rust 通过 `借用(Borrowing)` 这个概念来达成上述的目的 **获取变量的引用,称之为借用(borrowing)**。正如现实生活中,如果一个人拥有某样东西,你可以从他那里借来,当使用完毕后,也必须要物归原主。
@ -70,7 +70,7 @@ fn calculate_length(s: &String) -> usize { // s 是对 String 的引用
// 所以什么也不会发生
```
人总是贪心的,可以拉女孩小手了,就想着抱抱柔软的身子(读者中的某老司机表示,这个流程完全不对),因此光借用已经满足不了我们了,如果尝试修改借用的变量呢?
人总是贪心的,可以拉女孩小手了,就想着抱抱柔软的身子(读者中的某老司机表示,这个流程完全不对),因此光借用已经满足不了我们了,如果尝试修改借用的变量呢?
```rust
fn main() {
let s = String::from("hello");
@ -146,6 +146,7 @@ error[E0499]: cannot borrow `s` as mutable more than once at a time 同一时间
对于新手来说,这个特性绝对是一大拦路虎,也是新人们谈之色变的编译器 `borrow checker` 特性之一不过各行各业都一样限制往往是出于安全的考虑Rust 也一样。
这种限制的好处就是使 Rust 在编译期就避免数据竞争,数据竞争可由以下行为造成:
- 两个或更多的指针同时访问同一数据
- 至少有一个指针被用来写入数据
- 没有同步数据访问的机制
@ -224,7 +225,7 @@ fn main() {
虽然这种借用错误有的时候会让我们很郁闷,但是你只要想想这是 Rust 提前帮你发现了潜在的 BUG其实就开心了虽然减慢了开发速度但是从长期来看大幅减少了后续开发和运维成本。
### 悬垂引用Dangling References
### 悬垂引用(Dangling References)
悬垂引用也叫做悬垂指针,意思为指针指向某个值后,这个值被释放掉了,而指针仍然存在,其指向的内存可能不存在任何值或已被其它变量重新使用。在 Rust 中编译器可以确保引用永远也不会变成悬垂状态:当你拥有一些数据的引用,编译器可以确保数据不会在其引用之前被释放,要想释放数据,必须先停止其引用的使用。
@ -259,7 +260,7 @@ help: consider using the `'static` lifetime
```
错误信息引用了一个我们还未介绍的功能:[生命周期lifetimes](../../advance/lifetime/basic.md)。不过,即使你不理解生命周期,也可以通过错误信息知道这段代码错误的关键信息:
错误信息引用了一个我们还未介绍的功能:[生命周期(lifetimes)](https://course.rs/advance/lifetime/basic.html)。不过,即使你不理解生命周期,也可以通过错误信息知道这段代码错误的关键信息:
```text
this function's return type contains a borrowed value, but there is no value for it to be borrowed from.
@ -297,5 +298,6 @@ fn no_dangle() -> String {
## 借用规则总结
总的来说,借用规则如下:
- 同一时刻,你只能拥有要么一个可变引用, 要么任意多个不可变引用
- 引用必须总是有效的

@ -30,7 +30,7 @@ int* foo() {
在正式进入主题前,先来一个预热知识。
## 栈Stack与堆Heap
## 栈(Stack)与堆(Heap)
栈和堆是编程语言最核心的数据结构,但是在很多语言中,你并不需要深入了解栈与堆。 但对于 Rust 这样的系统编程语言,值是位于栈上还是堆上非常重要, 因为这会影响程序的行为和性能。
@ -49,11 +49,11 @@ int* foo() {
与栈不同,对于大小未知或者可能变化的数据,我们需要将它存储在堆上。
当向堆上放入数据时,需要请求一定大小的内存空间。操作系统在堆的某处找到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的**指针**, 该过程被称为**在堆上分配内存**,有时简称为 “分配”allocating
当向堆上放入数据时,需要请求一定大小的内存空间。操作系统在堆的某处找到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的**指针**, 该过程被称为**在堆上分配内存**,有时简称为 “分配”(allocating)
接着,该指针会被推入**栈**中,因为指针的大小是已知且固定的,在后续使用过程中,你将通过栈中的**指针**,来获取数据在堆上的实际内存位置,进而访问该数据。
由上可知,堆是一种缺乏组织的数据结构。想象一下去餐馆就座吃饭: 进入餐馆,告知服务员有几个人,然后服务员找到一个够大的空桌子(堆上分配的内存空间)并领你们过去。如果有人来迟了,他们也可以通过桌号(栈上的指针)来找到你们坐在哪。
由上可知,堆是一种缺乏组织的数据结构。想象一下去餐馆就座吃饭: 进入餐馆,告知服务员有几个人,然后服务员找到一个够大的空桌子(堆上分配的内存空间)并领你们过去。如果有人来迟了,他们也可以通过桌号(栈上的指针)来找到你们坐在哪。
#### 性能区别
@ -77,7 +77,7 @@ int* foo() {
理解了堆栈,接下来看一下*关于所有权的规则*,首先请谨记以下规则:
> 1. Rust 中每一个值都 `有且只有` 一个所有者(变量)
> 2. 当所有者(变量)离开作用域范围时,这个值将被丢弃(free)
> 2. 当所有者(变量)离开作用域范围时,这个值将被丢弃(drop)
@ -189,9 +189,9 @@ error[E0382]: use of moved value: `s1`
现在再回头看看之前的规则,相信大家已经有了更深刻的理解:
> 1. Rust 中每一个值都 `有且只有` 一个所有者(变量)
> 2. 当所有者(变量)离开作用域范围时,这个值将被丢弃(free)
> 2. 当所有者(变量)离开作用域范围时,这个值将被丢弃(drop)
如果你在其他语言中听说过术语**浅拷贝( shallow copy )**和**深拷贝( deep copy )**,那么拷贝指针、长度和容量而不拷贝数据听起来就像浅拷贝,但是又因为 Rust 同时使第一个变量 `s1` 无效了,因此这个操作被称为**移动move**,而不是浅拷贝。上面的例子可以解读为 `s1` 被**移动**到了 `s2` 中。那么具体发生了什么,用一张图简单说明:
如果你在其他语言中听说过术语 **浅拷贝(shallow copy)** **深拷贝(deep copy)**,那么拷贝指针、长度和容量而不拷贝数据听起来就像浅拷贝,但是又因为 Rust 同时使第一个变量 `s1` 无效了,因此这个操作被称为 **移动(move)**,而不是浅拷贝。上面的例子可以解读为 `s1` 被**移动**到了 `s2` 中。那么具体发生了什么,用一张图简单说明:
<img alt="s1 moved to s2" src="https://pic1.zhimg.com/80/v2-3ec77951de6a17584b5eb4a3838b4b61_1440w.jpg" class="center" style="width: 50%;" />
@ -316,5 +316,5 @@ fn takes_and_gives_back(a_string: String) -> String { // a_string 进入作用
```
所有权很强大,避免了内存的不安全性,但是也带来了一个新麻烦:**总是把一个值传来传去来使用它**。 传入一个函数很可能还要从该函数传出去结果就是语言表达变得非常啰嗦幸运的是Rust 提供了新功能解决这个问题。
所有权很强大,避免了内存的不安全性,但是也带来了一个新麻烦 **总是把一个值传来传去来使用它**。 传入一个函数很可能还要从该函数传出去结果就是语言表达变得非常啰嗦幸运的是Rust 提供了新功能解决这个问题。

Loading…
Cancel
Save