diff --git a/src/basic/compound-type/array.md b/src/basic/compound-type/array.md index 16e57fbd..b334a1ea 100644 --- a/src/basic/compound-type/array.md +++ b/src/basic/compound-type/array.md @@ -113,7 +113,7 @@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace #### 数组元素为非基础类型 -学习了上面的知识,很多朋友肯定觉得已经学会了Rust的数组类型,但现实会给我们一记重锤,实际开发中还会碰到一种情况,就是**数组元素是非基本类型**的,这时候大家一定会这样写。 +学习了上面的知识,很多朋友肯定觉得已经学会了 Rust 的数组类型,但现实会给我们一记重锤,实际开发中还会碰到一种情况,就是**数组元素是非基本类型**的,这时候大家一定会这样写。 ```rust let array = [String::from("rust is good!"); 8]; @@ -133,7 +133,7 @@ error[E0277]: the trait bound `String: std::marker::Copy` is not satisfied = note: the `Copy` trait is required because this value will be copied for each element of the array ``` -有些还没有看过特征的小伙伴,有可能不太明白这个报错,不过这个目前可以不提,我们就拿之前所学的[所有权](https://course.rs/basic/ownership/ownership.html)知识,就可以思考明白,前面几个例子都是Rust的基本类型,而**基本类型在Rust中赋值是以Copy的形式**,这时候你就懂了吧,`let array=[3;5]`底层就是不断的Copy出来的,但很可惜复杂类型都没有深拷贝,只能一个个创建。 +有些还没有看过特征的小伙伴,有可能不太明白这个报错,不过这个目前可以不提,我们就拿之前所学的[所有权](https://course.rs/basic/ownership/ownership.html)知识,就可以思考明白,前面几个例子都是 Rust 的基本类型,而**基本类型在 Rust 中赋值是以 Copy 的形式**,这时候你就懂了吧,`let array=[3;5]`底层就是不断的Copy出来的,但很可惜复杂类型都没有深拷贝,只能一个个创建。 接着就有小伙伴会这样写。 @@ -169,7 +169,7 @@ assert_eq!(slice, &[2, 3]); - 切片的长度可以与数组不同,并不是固定的,而是取决于你使用时指定的起始和结束位置 - 创建切片的代价非常小,因为切片只是针对底层数组的一个引用 -- 切片类型[T]拥有不固定的大小,而切片引用类型&[T]则具有固定的大小,因为 Rust 很多时候都需要固定大小数据类型,因此&[T]更有用,`&str`字符串切片也同理 +- 切片类型 [T] 拥有不固定的大小,而切片引用类型 &[T] 则具有固定的大小,因为 Rust 很多时候都需要固定大小数据类型,因此 &[T] 更有用,`&str` 字符串切片也同理 ## 总结 @@ -208,7 +208,7 @@ fn main() { 做个总结,数组虽然很简单,但是其实还是存在几个要注意的点: -- **数组类型容易跟数组切片混淆**,[T;n]描述了一个数组的类型,而[T]描述了切片的类型, 因为切片是运行期的数据结构,它的长度无法在编译期得知,因此不能用[T;n]的形式去描述 +- **数组类型容易跟数组切片混淆**,[T;n] 描述了一个数组的类型,而 [T] 描述了切片的类型, 因为切片是运行期的数据结构,它的长度无法在编译期得知,因此不能用 [T;n] 的形式去描述 - `[u8; 3]`和`[u8; 4]`是不同的类型,数组的长度也是类型的一部分 - **在实际开发中,使用最多的是数组切片[T]**,我们往往通过引用的方式去使用`&[T]`,因为后者有固定的类型大小 diff --git a/src/basic/compound-type/enum.md b/src/basic/compound-type/enum.md index f8b61660..7f480333 100644 --- a/src/basic/compound-type/enum.md +++ b/src/basic/compound-type/enum.md @@ -18,7 +18,7 @@ enum PokerSuit { 任何一张扑克,它的花色肯定会落在四种花色中,而且也只会落在其中一个花色上,这种特性非常适合枚举的使用,因为**枚举值**只可能是其中某一个成员。抽象来看,四种花色尽管是不同的花色,但是它们都是扑克花色这个概念,因此当某个函数处理扑克花色时,可以把它们当作相同的类型进行传参。 细心的读者应该注意到,我们对之前的 `枚举类型` 和 `枚举值` 进行了重点标注,这是因为对于新人来说容易混淆相应的概念,总而言之: -**枚举类型是一个类型,它会包含所有可能的枚举成员, 而枚举值是该类型中的具体某个成员的实例。** +**枚举类型是一个类型,它会包含所有可能的枚举成员,而枚举值是该类型中的具体某个成员的实例。** ## 枚举值 @@ -134,7 +134,7 @@ enum IpAddr { 这个例子跟我们之前的扑克牌很像,只不过枚举成员包含的类型更复杂了,变成了结构体:分别通过 `Ipv4Addr` 和 `Ipv6Addr` 来定义两种不同的 IP 数据。 -从这些例子可以看出,**任何类型的数据都可以放入枚举成员中**: 例如字符串、数值、结构体甚至另一个枚举。 +从这些例子可以看出,**任何类型的数据都可以放入枚举成员中**:例如字符串、数值、结构体甚至另一个枚举。 增加一些挑战?先看以下代码: @@ -207,7 +207,7 @@ enum Websocket { ## Option 枚举用于处理空值 -在其它编程语言中,往往都有一个 `null` 关键字,该关键字用于表明一个变量当前的值为空(不是零值,例如整型的零值是 0),也就是不存在值。当你对这些 `null` 进行操作时,例如调用一个方法,就会直接抛出**null 异常**,导致程序的崩溃,因此我们在编程时需要格外的小心去处理这些 `null` 空值。 +在其它编程语言中,往往都有一个 `null` 关键字,该关键字用于表明一个变量当前的值为空(不是零值,例如整型的零值是 0),也就是不存在值。当你对这些 `null` 进行操作时,例如调用一个方法,就会直接抛出 **null 异常**,导致程序的崩溃,因此我们在编程时需要格外的小心去处理这些 `null` 空值。 > Tony Hoare, `null` 的发明者,曾经说过一段非常有名的话: > diff --git a/src/basic/compound-type/intro.md b/src/basic/compound-type/intro.md index 77dcd2bb..0c94879d 100644 --- a/src/basic/compound-type/intro.md +++ b/src/basic/compound-type/intro.md @@ -30,7 +30,7 @@ fn main() { } ``` -接下来我们的学习非常类似原型设计:有的方法只提供 API 接口,但是不提供具体实现。此外,有的变量在声明之后并未使用,因此在这个阶段我们需要排除一些编译器噪音(Rust 在编译的时候会扫描代码,变量声明后未使用会以 `warning` 警告的形式进行提示),引入 `#![allow(unused_variables)]` 属性标记,该标记会告诉编译器忽略未使用的变量,不要抛出 `warning` 警告,具体的常见编译器属性你可以在这里查阅:[编译器属性标记](https://course.rs/profiling/compiler/attributes.html)。 +接下来我们的学习非常类似原型设计:有的方法只提供 API 接口,但是不提供具体实现。此外,有的变量在声明之后并未使用,因此在这个阶段我们需要排除一些编译器噪音(Rust 在编译的时候会扫描代码,变量声明后未使用会以 `warning` 警告的形式进行提示),引入 `#![allow(unused_variables)]` 属性标记,该标记会告诉编译器忽略未使用的变量,不要抛出 `warning` 警告,具体的常见编译器属性你可以在这里查阅:[编译器属性标记](https://course.rs/profiling/compiler/attributes.html)。 `read` 函数也非常有趣,它返回一个 `!` 类型,这个表明该函数是一个发散函数,不会返回任何值,包括 `()`。`unimplemented!()` 告诉编译器该函数尚未实现,`unimplemented!()` 标记通常意味着我们期望快速完成主要代码,回头再通过搜索这些标记来完成次要代码,类似的标记还有 `todo!()`,当代码执行到这种未实现的地方时,程序会直接报错。你可以反注释 `read(&mut f1, &mut vec![]);` 这行,然后再观察下结果。 diff --git a/src/basic/compound-type/string-slice.md b/src/basic/compound-type/string-slice.md index 85b55d46..85cb6c6a 100644 --- a/src/basic/compound-type/string-slice.md +++ b/src/basic/compound-type/string-slice.md @@ -95,9 +95,9 @@ let slice = &s[..]; > ``` > > 因为我们只取 `s` 字符串的前两个字节,但是本例中每个汉字占用三个字节,因此没有落在边界处,也就是连 `中` 字都取不完整,此时程序会直接崩溃退出,如果改成 `&s[0..3]`,则可以正常通过编译。 -> 因此,当你需要对字符串做切片索引操作时,需要格外小心这一点, 关于该如何操作 UTF-8 字符串,参见[这里](#操作-utf-8-字符串)。 +> 因此,当你需要对字符串做切片索引操作时,需要格外小心这一点,关于该如何操作 UTF-8 字符串,参见[这里](#操作-utf-8-字符串)。 -字符串切片的类型标识是 `&str`,因此我们可以这样声明一个函数,输入 `String` 类型,返回它的切片: `fn first_word(s: &String) -> &str `。 +字符串切片的类型标识是 `&str`,因此我们可以这样声明一个函数,输入 `String` 类型,返回它的切片:`fn first_word(s: &String) -> &str `。 有了切片就可以写出这样的代码: @@ -152,7 +152,7 @@ assert_eq!(slice, &[2, 3]); ## 字符串字面量是切片 -之前提到过字符串字面量,但是没有提到它的类型: +之前提到过字符串字面量,但是没有提到它的类型: ```rust let s = "Hello, world!"; @@ -705,9 +705,9 @@ for b in "中国人".bytes() { // s 不再有效,内存被释放 ``` -与其它系统编程语言的 `free` 函数相同,Rust 也提供了一个释放内存的函数: `drop`,但是不同的是,其它语言要手动调用 `free` 来释放每一个变量占用的内存,而 Rust 则在变量离开作用域时,自动调用 `drop` 函数: 上面代码中,Rust 在结尾的 `}` 处自动调用 `drop`。 +与其它系统编程语言的 `free` 函数相同,Rust 也提供了一个释放内存的函数: `drop`,但是不同的是,其它语言要手动调用 `free` 来释放每一个变量占用的内存,而 Rust 则在变量离开作用域时,自动调用 `drop` 函数:上面代码中,Rust 在结尾的 `}` 处自动调用 `drop`。 -> 其实,在 C++ 中,也有这种概念: _Resource Acquisition Is Initialization (RAII)_。如果你使用过 RAII 模式的话应该对 Rust 的 `drop` 函数并不陌生。 +> 其实,在 C++ 中,也有这种概念:_Resource Acquisition Is Initialization (RAII)_。如果你使用过 RAII 模式的话应该对 Rust 的 `drop` 函数并不陌生。 这个模式对编写 Rust 代码的方式有着深远的影响,在后面章节我们会进行更深入的介绍。 diff --git a/src/basic/compound-type/struct.md b/src/basic/compound-type/struct.md index 15c41821..698a5229 100644 --- a/src/basic/compound-type/struct.md +++ b/src/basic/compound-type/struct.md @@ -125,7 +125,7 @@ fn build_user(email: String, username: String) -> User { > 聪明的读者肯定要发问了:明明有三个字段进行了自动赋值,为何只有 `username` 发生了所有权转移? > > 仔细回想一下[所有权](https://course.rs/basic/ownership/ownership.html#拷贝浅拷贝)那一节的内容,我们提到了 `Copy` 特征:实现了 `Copy` 特征的类型无需所有权转移,可以直接在赋值时进行 -> 数据拷贝,其中 `bool` 和 `u64` 类型就实现了 `Copy` 特征,因此 `active` 和 `sign_in_count` 字段在赋值给 `user2` 时,仅仅发生了拷贝,而不是所有权转移。 +>数据拷贝,其中 `bool` 和 `u64` 类型就实现了 `Copy` 特征,因此 `active` 和 `sign_in_count` 字段在赋值给 `user2` 时,仅仅发生了拷贝,而不是所有权转移。 > > 值得注意的是:`username` 所有权被转移给了 `user2`,导致了 `user1` 无法再被使用,但是并不代表 `user1` 内部的其它字段不能被继续使用,例如: @@ -184,7 +184,7 @@ println!("{:?}", user1); 上面定义的 `File` 结构体在内存中的排列如下图所示: -从图中可以清晰地看出 `File` 结构体两个字段 `name` 和 `data` 分别拥有底层两个 `[u8]` 数组的所有权(`String` 类型的底层也是 `[u8]` 数组),通过 `ptr` 指针指向底层数组的内存地址,这里你可以把 `ptr` 指针理解为 Rust 中的引用类型。 +从图中可以清晰地看出 `File` 结构体两个字段 `name` 和 `data` 分别拥有底层两个 `[u8]` 数组的所有权(`String` 类型的底层也是 `[u8]` 数组),通过 `ptr` 指针指向底层数组的内存地址,这里你可以把 `ptr` 指针理解为 Rust 中的引用类型。 该图片也侧面印证了:**把结构体中具有所有权的字段转移出去后,将无法再访问该字段,但是可以正常访问其它的字段**。 @@ -206,7 +206,7 @@ println!("{:?}", user1); 还记得之前讲过的基本没啥用的[单元类型](https://course.rs/basic/base-type/char-bool.html#单元类型)吧?单元结构体就跟它很像,没有任何字段和属性,但是好在,它还挺有用。 -如果你定义一个类型,但是不关心该类型的内容, 只关心它的行为时,就可以使用 `单元结构体`: +如果你定义一个类型,但是不关心该类型的内容,只关心它的行为时,就可以使用 `单元结构体`: ```rust struct AlwaysEqual; diff --git a/src/basic/flow-control.md b/src/basic/flow-control.md index 2f2eef1b..8309a2ac 100644 --- a/src/basic/flow-control.md +++ b/src/basic/flow-control.md @@ -8,7 +8,7 @@ > if else 无处不在 -- 鲁迅 -但凡你能找到一门编程语言没有 `if else`,那么一定更要反馈给鲁迅,反正不是我说的:) 总之,只要你拥有其它语言的编程经验,就一定会有以下认知:`if else` **表达式**根据条件执行不同的代码分支: +但凡你能找到一门编程语言没有 `if else`,那么一定更要反馈给鲁迅,反正不是我说的 :) 总之,只要你拥有其它语言的编程经验,就一定会有以下认知:`if else` **表达式**根据条件执行不同的代码分支: ```rust if condition == true { @@ -38,7 +38,7 @@ fn main() { 以上代码有以下几点要注意: - **`if` 语句块是表达式**,这里我们使用 `if` 表达式的返回值来给 `number` 进行赋值:`number` 的值是 `5` -- 用 `if` 来赋值时,要保证每个分支返回的类型一样(事实上,这种说法不完全准确,见[这里](https://course.rs/appendix/expressions.html#if表达式)),此处返回的 `5` 和 `6` 就是同一个类型,如果返回类型不一致就会报错 +- 用 `if` 来赋值时,要保证每个分支返回的类型一样(事实上,这种说法不完全准确,见[这里](https://course.rs/appendix/expressions.html#if表达式)),此处返回的 `5` 和 `6` 就是同一个类型,如果返回类型不一致就会报错 ```console error[E0308]: if and else have incompatible types @@ -118,7 +118,7 @@ for item in &container { } ``` -> 对于实现了 `copy` 特征的数组(例如 [i32; 10] )而言, `for item in arr` 并不会把 `arr` 的所有权转移,而是直接对其进行了拷贝,因此循环之后仍然可以使用 `arr` 。 +> 对于实现了 `copy` 特征的数组(例如 [i32; 10])而言, `for item in arr` 并不会把 `arr` 的所有权转移,而是直接对其进行了拷贝,因此循环之后仍然可以使用 `arr` 。 如果想在循环中,**修改该元素**,可以使用 `mut` 关键字: diff --git a/src/basic/match-pattern/match-if-let.md b/src/basic/match-pattern/match-if-let.md index 8b38ec0b..1c13d13c 100644 --- a/src/basic/match-pattern/match-if-let.md +++ b/src/basic/match-pattern/match-if-let.md @@ -144,7 +144,7 @@ fn value_in_cents(coin: Coin) -> u8 { 上面代码中,在匹配 `Coin::Quarter(state)` 模式时,我们把它内部存储的值绑定到了 `state` 变量上,因此 `state` 变量就是对应的 `UsState` 枚举类型。 -例如有一个印了阿拉斯加州标记的 25 分硬币:`Coin::Quarter(UsState::Alaska)`, 它在匹配时,`state` 变量将被绑定 `UsState::Alaska` 的枚举值。 +例如有一个印了阿拉斯加州标记的 25 分硬币:`Coin::Quarter(UsState::Alaska)`,它在匹配时,`state` 变量将被绑定 `UsState::Alaska` 的枚举值。 再来看一个更复杂的例子: @@ -236,7 +236,7 @@ error[E0004]: non-exhaustive patterns: `West` not covered // 非穷尽匹配,` = note: the matched value is of type `Direction` ``` -不禁想感叹,Rust 的编译器**真强大**,忍不住想爆粗口了,sorry,如果你以后进一步深入使用 Rust 也会像我这样感叹的。Rust 编译器清晰地知道 `match` 中有哪些分支没有被覆盖, 这种行为能强制我们处理所有的可能性,有效避免传说中价值**十亿美金**的 `null` 陷阱。 +不禁想感叹,Rust 的编译器**真强大**,忍不住想爆粗口了,sorry,如果你以后进一步深入使用 Rust 也会像我这样感叹的。Rust 编译器清晰地知道 `match` 中有哪些分支没有被覆盖,这种行为能强制我们处理所有的可能性,有效避免传说中价值**十亿美金**的 `null` 陷阱。 #### `_` 通配符 @@ -342,7 +342,7 @@ assert!(matches!(bar, Some(x) if x > 2)); ## 变量遮蔽 -无论是 `match` 还是 `if let`,这里都是一个新的代码块,而且这里的绑定相当于新变量,如果你使用同名变量,会发生变量遮蔽: +无论是 `match` 还是 `if let`,这里都是一个新的代码块,而且这里的绑定相当于新变量,如果你使用同名变量,会发生变量遮蔽: ```rust fn main() { diff --git a/src/basic/match-pattern/option.md b/src/basic/match-pattern/option.md index 634a46db..12714b98 100644 --- a/src/basic/match-pattern/option.md +++ b/src/basic/match-pattern/option.md @@ -32,7 +32,7 @@ let six = plus_one(five); let none = plus_one(None); ``` -`plus_one` 接受一个 `Option` 类型的参数,同时返回一个 `Option` 类型的值(这种形式的函数在标准库内随处所见),在该函数的内部处理中,如果传入的是一个 `None` ,则返回一个 `None` 且不做任何处理;如果传入的是一个 `Some(i32)`,则通过模式绑定,把其中的值绑定到变量 `i` 上,然后返回 `i+1` 的值,同时用 `Some` 进行包裹。 +`plus_one` 接受一个 `Option` 类型的参数,同时返回一个 `Option` 类型的值(这种形式的函数在标准库内随处所见),在该函数的内部处理中,如果传入的是一个 `None` ,则返回一个 `None` 且不做任何处理;如果传入的是一个 `Some(i32)`,则通过模式绑定,把其中的值绑定到变量 `i` 上,然后返回 `i+1` 的值,同时用 `Some` 进行包裹。 为了进一步说明,假设 `plus_one` 函数接受的参数值 x 是 `Some(5)`,来看看具体的分支匹配情况: diff --git a/src/basic/match-pattern/pattern-match.md b/src/basic/match-pattern/pattern-match.md index 3e823445..568afa8e 100644 --- a/src/basic/match-pattern/pattern-match.md +++ b/src/basic/match-pattern/pattern-match.md @@ -89,13 +89,13 @@ let PATTERN = EXPRESSION; let x = 5; ``` -这其中,`x` 也是一种模式绑定,代表将**匹配的值绑定到变量 x 上**。因此,在 Rust 中,**变量名也是一种模式**,只不过它比较朴素很不起眼罢了。 +这其中,`x` 也是一种模式绑定,代表将**匹配的值绑定到变量 x 上**。因此,在 Rust 中,**变量名也是一种模式**,只不过它比较朴素很不起眼罢了。 ```rust let (x, y, z) = (1, 2, 3); ``` -上面将一个元组与模式进行匹配(**模式和值的类型必需相同!**),然后把 `1, 2, 3` 分别绑定到 `x, y, z` 上。 +上面将一个元组与模式进行匹配(**模式和值的类型必需相同!**),然后把 `1, 2, 3` 分别绑定到 `x, y, z` 上。 模式匹配要求两边的类型必须相同,否则就会导致下面的报错: