Update: unified format

pull/280/head
Allan Downey 3 years ago
parent 1377c9c9df
commit f2b78b8b13

@ -1,25 +1,25 @@
# 基本类型 # 基本类型
当一门语言不谈类型时,你得小心,这大概率是动态语言(别拍我,我承认是废话)。但是把类型大张旗鼓的用多个章节去讲的Rust是其中之一。 当一门语言不谈类型时,你得小心,这大概率是动态语言(别拍我,我承认是废话)。但是把类型大张旗鼓的用多个章节去讲的Rust 是其中之一。
Rust每个值都有其确切的数据类型, 总的来说可以分为两类:基本类型和复合类型。 基本类型意味着它们往往是一个最小化原子类型,无法解构为其它类型(一般意义上来说),由以下组成: Rust 每个值都有其确切的数据类型,总的来说可以分为两类:基本类型和复合类型。 基本类型意味着它们往往是一个最小化原子类型,无法解构为其它类型(一般意义上来说),由以下组成:
- 数值类型: 有符号整数 (`i8`, `i16`, `i32`, `i64`, `isize`)、 无符号整数 (`u8`, `u16`, `u32`, `u64`, `usize`) 、浮点数 (`f32`, `f64`)、以及有理数、复数 - 数值类型: 有符号整数 (`i8`, `i16`, `i32`, `i64`, `isize`)、 无符号整数 (`u8`, `u16`, `u32`, `u64`, `usize`) 、浮点数 (`f32`, `f64`)、以及有理数、复数
- 字符串:字符串字面量和字符串切片&str - 字符串:字符串字面量和字符串切片 `&str`
- 布尔类型: `true`和`false` - 布尔类型: `true`和`false`
- 字符类型: 表示单个Unicode字符存储为4个字节 - 字符类型: 表示单个Unicode字符存储为4个字节
- 元类型: 即`()`,其唯一的值也是`()` - 元类型: 即 `()` ,其唯一的值也是 `()`
## 类型推导与标注 ## 类型推导与标注
`python`、`javascript`等动态语言不同Rust是一门静态类型语言,也就是编译器必须在编译期知道我们所有变量的类型,但这不意味着你需要为每个变量指定类型,因为**Rust编译器很聪明它可以根据变量的值和上下文中的使用方式来自动推导出变量的类型**,同时编译器也不够聪明,在某些情况下,它无法推导出变量类型,需要手动去给予一个类型标注,关于这一点在[Rust语言初印象](../../first-try/hello-world.md#Rust语言初印象)中有过展示. `Python``Javascript` 等动态语言不同Rust 是一门静态类型语言,也就是编译器必须在编译期知道我们所有变量的类型,但这不意味着你需要为每个变量指定类型,因为**Rust 编译器很聪明,它可以根据变量的值和上下文中的使用方式来自动推导出变量的类型**,同时编译器也不够聪明,在某些情况下,它无法推导出变量类型,需要手动去给予一个类型标注,关于这一点在[Rust语言初印象](../../first-try/hello-world.md#Rust语言初印象)中有过展示
来看段代码: 来看段代码
```rust ```rust
let guess = "42".parse().expect("Not a number!"); let guess = "42".parse().expect("Not a number!");
``` ```
先忽略`.parse().expect..`部分,这段代码的目的是将字符串`"42"`进行解析,而编译器在这里无法推导出我们想要的类型:整数?浮点数?字符串?因此编译器会报错: 先忽略 `.parse().expect..` 部分,这段代码的目的是将字符串 `"42"` 进行解析,而编译器在这里无法推导出我们想要的类型:整数?浮点数?字符串?因此编译器会报错:
```console ```console
$ cargo build $ cargo build
Compiling no_type_annotations v0.1.0 (file:///projects/no_type_annotations) Compiling no_type_annotations v0.1.0 (file:///projects/no_type_annotations)
@ -30,5 +30,5 @@ error[E0282]: type annotations needed
| ^^^^^ consider giving `guess` a type | ^^^^^ consider giving `guess` a type
``` ```
因此我们需要提供给编译器更多的信息,例如给`guess`变量一个**显式的类型标注**: `let guess: i32 = ...` 或者`"42".parse::<i32>()`. 因此我们需要提供给编译器更多的信息,例如给 `guess` 变量一个**显式的类型标注** `let guess: i32 = ...` 或者 `"42".parse::<i32>()`

@ -1,17 +1,17 @@
# 数值类型 # 数值类型
我朋友有一个领导(读者:你朋友?黑人问号)说过一句话所有代码就是0和1简单的很。咱不评价这句话的正确性但是计算机底层由01组成倒是真的。 我朋友有一个领导(读者:你朋友?黑人问号)说过一句话:所有代码就是 0 和 1 ,简单的很。咱不评价这句话的正确性,但是计算机底层由 01 组成倒是真的。
计算机和数值关联在一起的时间远比我们想象的要长因此数值类型可以说是有计算机以来就有的类型下面内容将深入讨论Rust的数值类型以及相关的运算符。 计算机和数值关联在一起的时间,远比我们想象的要长,因此数值类型可以说是有计算机以来就有的类型,下面内容将深入讨论 Rust 的数值类型以及相关的运算符。
## 整数和浮点数 ## 整数和浮点数
Rust使用一个相对传统的语法来创建整数(`1`,`2`,...)和浮点数(`1.0`,`1.1`,...)。整数、浮点数的运算和你在其它语言上见过的一致,都是通过常见的运算符来完成。 Rust 使用一个相对传统的语法来创建整数(`1`, `2`,...)和浮点数(`1.0`,`1.1`,...)。整数、浮点数的运算和你在其它语言上见过的一致,都是通过常见的运算符来完成。
> 不仅仅是数值类型Rust也允许在复杂类型上定义运算符例如在自定义类型上定义`+`运算符,这种行为被称为运算符重载, Rust具体支持的可重载运算符见[这里](../../appendix/operators.md#运算符) > 不仅仅是数值类型Rust 也允许在复杂类型上定义运算符,例如在自定义类型上定义 `+` 运算符这种行为被称为运算符重载Rust 具体支持的可重载运算符见[这里](../../appendix/operators.md#运算符)
#### 整数类型 #### 整数类型
**整数**是没有小数部分的数字。之前使用过的`i32`类型, 表示有符号的32位整数`i` 是英文单词 *integer* 的首字母,与之相反的是 `u`,代表无符号 `unsigned` 类型)。下表显示了 Rust 中的内置的整数类型: **整数**是没有小数部分的数字。之前使用过的 `i32` 类型,表示有符号的 32位 整数( `i` 是英文单词 *integer* 的首字母,与之相反的是 `u`,代表无符号 `unsigned` 类型)。下表显示了 Rust 中的内置的整数类型:
| 长度 | 有符号类型 | 无符号类型 | | 长度 | 有符号类型 | 无符号类型 |
@ -23,12 +23,12 @@ Rust使用一个相对传统的语法来创建整数(`1`,`2`,...)和浮点数(`1
| 128-位 | `i128` | `u128` | | 128-位 | `i128` | `u128` |
| 视架构而定 | `isize` | `usize` | | 视架构而定 | `isize` | `usize` |
类型定义的形式统一为:有无符号 + 类型大小(位数)。**无符号数**表示数字只能取正数,而**有符号**则表示数字即可以取正数又可以取负数。就像在纸上写数字一样:当要强调符号时,数字前面可以带上正号或负号;然而,当很明显确定数字为正数时,就不需要加上正号了。有符号数字以[补码](https://en.wikipedia.org/wiki/Two%27s_complement)形式存储。 类型定义的形式统一为:`有无符号 + 类型大小(位数)`。**无符号数**表示数字只能取正数,而**有符号**则表示数字即可以取正数又可以取负数。就像在纸上写数字一样:当要强调符号时,数字前面可以带上正号或负号;然而,当很明显确定数字为正数时,就不需要加上正号了。有符号数字以[补码](https://en.wikipedia.org/wiki/Two%27s_complement)形式存储。
每个有符号类型规定的数字范围是 -(2<sup>n - 1</sup>) ~ 2<sup>n - 每个有符号类型规定的数字范围是 -(2<sup>n - 1</sup>) ~ 2<sup>n -
1</sup> - 1其中 `n` 是该定义形式的位长度。因此 `i8` 可存储数字范围是 -(2<sup>7</sup>) ~ 2<sup>7</sup> - 1即 -128 ~ 127。无符号类型可以存储的数字范围是 0 ~ 2<sup>n</sup> - 1所以 `u8` 能够存储的数字为 0 ~ 2<sup>8</sup> - 1即 0 ~ 255。 1</sup> - 1其中 `n` 是该定义形式的位长度。因此 `i8` 可存储数字范围是 -(2<sup>7</sup>) ~ 2<sup>7</sup> - 1即 -128 ~ 127。无符号类型可以存储的数字范围是 0 ~ 2<sup>n</sup> - 1所以 `u8` 能够存储的数字为 0 ~ 2<sup>8</sup> - 1即 0 ~ 255。
此外,`isize` 和 `usize` 类型取决于程序运行的计算机cpu类型 若cpu是32位的则这两个类型是32位的同理若cpu是64位那么它们则是64位. 此外,`isize` 和 `usize` 类型取决于程序运行的计算机 CPU 类型: 若 CPU 是 32 位的,则这两个类型是 32 位的,同理,若 CPU 是 64 位,那么它们则是 64 位。
整形字面量可以用下表的形式书写: 整形字面量可以用下表的形式书写:
@ -42,13 +42,13 @@ Rust使用一个相对传统的语法来创建整数(`1`,`2`,...)和浮点数(`1
| 字节 (仅限于 `u8`) | `b'A'` | | 字节 (仅限于 `u8`) | `b'A'` |
这么多类型有没有一个简单的使用准则答案是肯定的Rust整形默认使用`i32`,例如`let i = 1`,那`i`就是`i32`类型,因此你可以首选它,同时该类型也往往是性能最好的。 `isize``usize` 的主要应用场景是用作集合的索引。 这么多类型,有没有一个简单的使用准则?答案是肯定的, Rust 整形默认使用 `i32`,例如 `let i = 1`,那 `i` 就是 `i32` 类型,因此你可以首选它,同时该类型也往往是性能最好的。`isize` 和 `usize` 的主要应用场景是用作集合的索引。
> ##### 整型溢出 > ##### 整型溢出
> >
> 比方说有一个 `u8` ,它可以存放从 0 到 255 的值。那么当你将其修改为范围之外的值,比如 256则会发生**整型溢出**。关于这一行为 Rust 有一些有趣的规则:当在 debug 模式编译时Rust 会检查整型溢出,若存在这些问题,则使程序在编译时 *panic*(崩溃,Rust 使用这个术语来表明程序因错误而退出)。 > 比方说有一个 `u8` ,它可以存放从 0 到 255 的值。那么当你将其修改为范围之外的值,比如 256则会发生**整型溢出**。关于这一行为 Rust 有一些有趣的规则:当在 debug 模式编译时Rust 会检查整型溢出,若存在这些问题,则使程序在编译时 *panic*(崩溃,Rust 使用这个术语来表明程序因错误而退出)。
> >
> 在当使用 `--release` 参数进行 release 模式构建时Rust **不**检测溢出。相反当检测到整型溢出时Rust 会按照补码循环溢出(*twos complement wrapping*)的规则处理。简而言之,大于该类型最大值的数值会被补码转换成该类型能够支持的对应数字的最小值。比如在 `u8` 的情况下256 变成 0257 变成 1依此类推。程序不会 panic但是该变量的值可能不是你期望的值。依赖这种默认行为的代码都应该被认为是错误的代码。 > 在当使用 `--release` 参数进行 release 模式构建时Rust **不**检测溢出。相反当检测到整型溢出时Rust 会按照补码循环溢出(*twos complement wrapping*)的规则处理。简而言之,大于该类型最大值的数值会被补码转换成该类型能够支持的对应数字的最小值。比如在 `u8` 的情况下256 变成 0257 变成 1依此类推。程序不会 *panic*,但是该变量的值可能不是你期望的值。依赖这种默认行为的代码都应该被认为是错误的代码。
> >
> 要显式处理可能的溢出,可以使用标准库针对原始数字类型提供的这些方法: > 要显式处理可能的溢出,可以使用标准库针对原始数字类型提供的这些方法:
> >
@ -100,7 +100,7 @@ fn main() {
这些语句中的每个表达式都使用了数学运算符,并且计算结果为一个值,然后绑定到一个变量上。[附录](../../appendix/operators.md)中给出了 Rust 提供的所有运算符的列表。 这些语句中的每个表达式都使用了数学运算符,并且计算结果为一个值,然后绑定到一个变量上。[附录](../../appendix/operators.md)中给出了 Rust 提供的所有运算符的列表。
再来看一个综合性的示例: 再来看一个综合性的示例
```rust ```rust
fn main() { fn main() {
@ -135,12 +135,12 @@ println!("{:02}", forty_twos[0]);
浮点数由于底层格式的特殊性,导致了如果在使用浮点数时不够谨慎,就可能造成危险,有两个原因: 浮点数由于底层格式的特殊性,导致了如果在使用浮点数时不够谨慎,就可能造成危险,有两个原因:
1. **浮点数往往是你想要数字的近似表达** 1. **浮点数往往是你想要数字的近似表达**
浮点数类型是基于二进制实现的,但是我们想要计算的数字往往是基于十进制,例如`0.1`在二进制上并不存在精确的表达形式,但是在十进制上就存在。这种不匹配性导致一定的歧义性,更多的,虽然浮点数能代表真实的数值,但是由于底层格式问题,它往往受限于定长的浮点数精度,如果你想要表达完全精准的真实数字,只有使用无限精度的浮点数才行 浮点数类型是基于二进制实现的,但是我们想要计算的数字往往是基于十进制,例如 `0.1` 在二进制上并不存在精确的表达形式,但是在十进制上就存在。这种不匹配性导致一定的歧义性,更多的,虽然浮点数能代表真实的数值,但是由于底层格式问题,它往往受限于定长的浮点数精度,如果你想要表达完全精准的真实数字,只有使用无限精度的浮点数才行
2. **浮点数在某些特性上是反直觉的** 2. **浮点数在某些特性上是反直觉的**
例如大家都会觉得浮点数可以进行比较,对吧?是的,它们确实可以使用`>`,`>=`等进行比较,但是在某些场景下,这种直觉上的比较特性反而会害了你。因为`f32`,`f64`上的比较运算实现的是`std::cmp::PartialEq`特征(类似其他语言的接口), 但是并没有实现`std::cmp::Eq`特征,但是后者在其它数值类型上都有定义,说了这么多,可能大家还是云里雾里,用一个例子来举例: 例如大家都会觉得浮点数可以进行比较,对吧?是的,它们确实可以使用 `>` `>=` 等进行比较,但是在某些场景下,这种直觉上的比较特性反而会害了你。因为 `f32` `f64` 上的比较运算实现的是 `std::cmp::PartialEq` 特征(类似其他语言的接口),但是并没有实现 `std::cmp::Eq` 特征,但是后者在其它数值类型上都有定义,说了这么多,可能大家还是云里雾里,用一个例子来举例:
Rust的HashMap数据结构是一个KV类型的hash map实现它对于`K`没有特定类型的限制,但是要求能用作`K`的类型必须实现了`std::cmp::Eq`特征,因此这意味着你无法使用浮点数作为`HashMap`的`Key`来存储键值对但是作为对比Rust的整数类型、字符串类型、布尔类型都实现了该特征因此可以作为`HashMap`的`Key`。 Rust`HashMap` 数据结构,是一个 KV 类型的 Hash Map 实现,它对于 `K` 没有特定类型的限制,但是要求能用作 `K` 的类型必须实现了 `std::cmp::Eq` 特征,因此这意味着你无法使用浮点数作为 `HashMap` `Key`来存储键值对但是作为对比Rust的整数类型、字符串类型、布尔类型都实现了该特征因此可以作为 `HashMap` `Key`
为了避免上面说的两个陷阱,你需要遵守以下准则: 为了避免上面说的两个陷阱,你需要遵守以下准则:
- 避免在浮点数上测试相等性 - 避免在浮点数上测试相等性
@ -154,11 +154,11 @@ fn main() {
} }
``` ```
你可能以为这段代码没啥问题吧实际上它会panic(程序崩溃,抛出异常)因为二进制精度问题导致了0.1 + 0.2并不严格等于0.3它们可能在小数点N位后存在误差。 你可能以为,这段代码没啥问题吧,实际上它会 *panic*(程序崩溃,抛出异常),因为二进制精度问题,导致了 0.1 + 0.2 并不严格等于 0.3,它们可能在小数点 N 位后存在误差。
那如果非要进行比较呢?可以考虑用这种方式`(0.1 + 0.2 - 0.3).abs() < 0.00001`,. 那如果非要进行比较呢?可以考虑用这种方式 `(0.1 + 0.2 - 0.3).abs() < 0.00001` ,具体小于多少,取决于你对精度的需求。
讲到这里,相信大家基本已经明白了,为什么操作浮点数时要格外的小心,但是还不够,下面再来一段代码,直接震撼你的灵魂: 讲到这里,相信大家基本已经明白了,为什么操作浮点数时要格外的小心,但是还不够,下面再来一段代码,直接震撼你的灵魂
```rust ```rust
fn main() { fn main() {
@ -197,15 +197,15 @@ note: run with `RUST_BACKTRACE=1` environment variable to display
➥a backtrace ➥a backtrace
``` ```
仔细看,对`f32`类型做加法时,`0.1+0.2`的结果是`3e99999a`0.3也是`3e99999a`,因此`f32`下的`0.1+0.2==0.3`通过测试,但是到了`f64`类型时结果就不一样了因为f64精度高很多因此在小数点非常后面发生了一点微小的变化`0.1+0.2`以`4`结尾但是0.3以`3`结尾,这个细微区别导致`f64`下的测试失败了,并且抛出了异常。 仔细看,对 `f32` 类型做加法时,`0.1 + 0.2` 的结果是 `3e99999a``0.3` 也是 `3e99999a`,因此 `f32` 下的 `0.1 + 0.2 == 0.3` 通过测试,但是到了 `f64` 类型时,结果就不一样了,因为 `f64` 精度高很多,因此在小数点非常后面发生了一点微小的变化,`0.1 + 0.2` `4` 结尾,但是 `0.3` 以`3`结尾,这个细微区别导致 `f64` 下的测试失败了,并且抛出了异常。
是不是**blow your mind away**? 没关系,在本书的后续章节中类似的直击灵魂的地方还很多,这就是敢号称`Rust语言圣经`的底气! 是不是**blow your mind away**? 没关系,在本书的后续章节中类似的直击灵魂的地方还很多,这就是敢号称 `Rust语言圣经Rust Course` 的底气!
#### NaN #### NaN
对于数学上未定义的结果,例如对负数取平方根`-42.1.sqrt()`会产生一个特殊的结果Rust的浮点数类型使用`NaN`(not a number)来处理这些情况。 对于数学上未定义的结果,例如对负数取平方根 `-42.1.sqrt()` 会产生一个特殊的结果Rust 的浮点数类型使用 `NaN` (not a number)来处理这些情况。
**所有跟`NaN`交互的操作,都会返回一个`NaN`**,而且`NaN`不能用来比较,下面的代码会崩溃: **所有跟 `NaN` 交互的操作,都会返回一个 `NaN`**,而且 `NaN` 不能用来比较,下面的代码会崩溃:
```rust ```rust
fn main() { fn main() {
@ -214,7 +214,7 @@ fn main() {
} }
``` ```
出于防御性编程的考虑,可以使用`is_nan()`等方法,可以用来判断一个数值是否是`NaN`: 出于防御性编程的考虑,可以使用 `is_nan()` 等方法,可以用来判断一个数值是否是 `NaN`
```rust ```rust
fn main() { fn main() {
@ -227,7 +227,8 @@ fn main() {
## 序列(Range) ## 序列(Range)
Rust提供了一个非常简洁的方式用来生成连续的数值例如`1..5`生成从1到4的连续数字不包含5; `1..=5`生成从1到5的连续数字,包含5它的用途很简单常常用于循环中 Rust 提供了一个非常简洁的方式,用来生成连续的数值,例如 `1..5` ,生成从 1 到 4 的连续数字,不包含 5 `1..=5` ,生成从 1 到 5 的连续数字,包含 5 ,它的用途很简单,常常用于循环中:
```rust ```rust
for i in 1..=5 { for i in 1..=5 {
println!("{}",i); println!("{}",i);
@ -253,18 +254,18 @@ for i in 'a'..='z' {
## 有理数和复数 ## 有理数和复数
Rust的标准库相比其它语言对于准入的门槛较高因此有理数和复数并未包含在标准库中: Rust 的标准库相比其它语言,对于准入的门槛较高,因此有理数和复数并未包含在标准库中
- 有理数和复数 - 有理数和复数
- 任意大小的整数和任意精度的浮点数 - 任意大小的整数和任意精度的浮点数
- 固定精度的十进制小数,常用于货币相关的场景 - 固定精度的十进制小数,常用于货币相关的场景
好在社区已经开发出高质量的Rust数值库[num](https://crates.io/crates/num). 好在社区已经开发出高质量的 Rust 数值库:[num](https://crates.io/crates/num).
按照以下步骤来引入`num`库: 按照以下步骤来引入 `num` 库:
1. 创建新工程`cargo new complex-num && cd complex-num` 1. 创建新工程 `cargo new complex-num && cd complex-num`
2. 在`Cargo.toml`中的`[dependencies]`下添加一行`num = "0.4.0"` 2. 在 `Cargo.toml` 中的 `[dependencies]` 下添加一行 `num = "0.4.0"`
3. 将`src/main.rs`文件中的`main`函数替换为下面的代码 3. 将 `src/main.rs` 文件中的 `main` 函数替换为下面的代码
4. 运行`cargo run` 4. 运行 `cargo run`
```rust ```rust
use num::complex::Complex; use num::complex::Complex;
@ -281,11 +282,11 @@ use num::complex::Complex;
## 总结 ## 总结
之前提到了过Rust的数值类型和运算跟其他语言较为相似但是实际上除了语法上的不同之外还是存在一些差异点: 之前提到了过 Rust 的数值类型和运算跟其他语言较为相似,但是实际上,除了语法上的不同之外,还是存在一些差异点
- **Rust拥有相当多的数值类型**. 因此你需要熟悉这些类型所占用的字节数,这样就知道该类型允许的大小范围以及你选择的类型是否能表达负数 - **Rust 拥有相当多的数值类型**. 因此你需要熟悉这些类型所占用的字节数,这样就知道该类型允许的大小范围以及你选择的类型是否能表达负数
- **类型转换必须是显式的**. Rust永远也不会偷偷把你的16bit整数转换成32bit整数 - **类型转换必须是显式的**. Rust 永远也不会偷偷把你的 16bit 整数转换成 32bit 整数
- **Rust的数值上可以使用方法**. 例如你可以用以下方法来将`13.14`取整: `13.14_f32.round()`, 在这里我们使用了类型后缀,因为编译器需要知道`13.14`的具体类型 - **Rust 的数值上可以使用方法**. 例如你可以用以下方法来将 `13.14` 取整: `13.14_f32.round()`在这里我们使用了类型后缀,因为编译器需要知道 `13.14 `的具体类型
数值类型的讲解已经基本结束,接下来,来看看字符和布尔类型。 数值类型的讲解已经基本结束,接下来,来看看字符和布尔类型。

@ -1,18 +1,18 @@
# Rust基本概念 # Rust 基本概念
从现在开始我们正式踏入了Rust大陆这篇广袤而神秘的世界在这个世界中将接触到很多之前都没有听过的概念 从现在开始,我们正式踏入了 Rust 大陆,这篇广袤而神秘的世界,在这个世界中,将接触到很多之前都没有听过的概念:
- 所有权、借用、生命周期 - 所有权、借用、生命周期
- 宏编程 - 宏编程
- 模式匹配 - 模式匹配
类似的还有很多,不过不用怕,引用武林外传一句话:咱上面有人。有本书在,一切虚妄终将烟消云散。 类似的还有很多,不过不用怕,引用武林外传一句话:咱上面有人。有本书在,一切虚妄终将烟消云散。
本章主要介绍Rust的基础语法、数据类型、项目结构等学完本章你将对Rust代码有一个清晰、完整的认识。 本章主要介绍 Rust 的基础语法、数据类型、项目结构等,学完本章,你将对 Rust 代码有一个清晰、完整的认识。
开始之前先通过一段代码来简单浏览下Rust的语法: 开始之前先通过一段代码来简单浏览下 Rust 的语法:
```rust ```rust
// rust程序入口函数跟其它语言一样都是main该函数目前无返回值 // Rust 程序入口函数,跟其它语言一样,都是 main该函数目前无返回值
fn main() { fn main() {
// 使用let来声明变量进行绑定a是不可变的 // 使用let来声明变量进行绑定a是不可变的
// 此处没有指定a的类型编译器会默认根据a的值为a推断类型i32有符号32位整数 // 此处没有指定a的类型编译器会默认根据a的值为a推断类型i32有符号32位整数
@ -43,8 +43,8 @@ fn main() {
``` ```
> 注意 > 注意
>在上面的`add`函数中,不要为`i+j`添加`;`,这会改变语法导致函数返回`()`而不是`i32`,具体参见[语句和表达式](./base-type/statement-expression.md) >在上面的 `add` 函数中,不要为 `i+j` 添加 `;`,这会改变语法导致函数返回 `()` 而不是 `i32`具体参见[语句和表达式](./base-type/statement-expression.md)
有几点可以留意下: 有几点可以留意下
- 字符串使用双引号`""`而不是单引号`''`, Rust中单引号是留给单个字符类型(`char`)使用的 - 字符串使用双引号 `""` 而不是单引号 `''`Rust 中单引号是留给单个字符类型(`char`使用的
- Rust使用`{}`来作为格式化输出占位符,其它语言可能使用的是`%s``%d`,`%p`等,由于`println!`会自动推导出具体的类型, 因此无需手动指定 - Rust 使用 `{}` 来作为格式化输出占位符,其它语言可能使用的是 `%s``%d``%p` 等,由于 `println!` 会自动推导出具体的类型, 因此无需手动指定

@ -1,34 +1,34 @@
# 变量绑定与解构 # 变量绑定与解构
鉴于本书的目标读者(别慌,来到这里就说明你就是目标读者)已经熟练掌握其它任意一门编程语言因此这里就不再对何为变量进行赘述让我们开门见山来谈谈为何Rust选择了手动设定变量的可变性。 鉴于本书的目标读者(别慌,来到这里就说明你就是目标读者)已经熟练掌握其它任意一门编程语言,因此这里就不再对何为变量进行赘述,让我们开门见山来谈谈,为何 Rust 选择了手动设定变量的可变性。
## 为何要手动设置变量的可变性? ## 为何要手动设置变量的可变性?
在其它大多数语言中,变量一旦创建,要么是可变的,要么是不可变的(ClosureScript)前者为编程提供了灵活性后者为编程提供了安全性而Rust比较野选择了两者我都要既要灵活性又要安全性。 在其它大多数语言中,变量一旦创建,要么是可变的,要么是不可变的(ClosureScript),前者为编程提供了灵活性,后者为编程提供了安全性,而 Rust 比较野,选择了两者我都要,既要灵活性又要安全性。
能想要学习Rust说明我们的读者都是相当有水平的程序员了你们应该能理解**一切选择皆是权衡**那么两者都要的权衡是什么呢这就是Rust开发团队为我们做出的贡献两者都要意味着Rust语言底层代码的实现复杂度大幅提升因此Respect to The Rust Team! 能想要学习 Rust说明我们的读者都是相当有水平的程序员了你们应该能理解**一切选择皆是权衡**,那么两者都要的权衡是什么呢?这就是 Rust 开发团队为我们做出的贡献,两者都要意味着 Rust 语言底层代码的实现复杂度大幅提升,因此 Respect to The Rust Team!
除了以上两个优点,还有一个很大的优点,那就是运行性能上的提升,因为将本身无需改变的变量声明为不可变在运行期会避免一些多余的`runtime`检查。 除了以上两个优点,还有一个很大的优点,那就是运行性能上的提升,因为将本身无需改变的变量声明为不可变在运行期会避免一些多余的 `runtime` 检查。
## 变量命名 ## 变量命名
在命名方面,和其它语言没有区别,不过当给变量命名时,需要遵循[Rust命名规范](../practice/style-guide/naming.md)。 在命名方面,和其它语言没有区别,不过当给变量命名时,需要遵循[Rust命名规范](../practice/style-guide/naming.md)。
> Rust语言有一些**关键字***keywords*和其他语言一样这些关键字都是被保留给Rust语言使用的因此它们不能被用作变量或函数的名称。 在[附录 A](../appendix/keywords) 中可找到关键字列表。 > Rust 语言有一些**关键字***keywords*),和其他语言一样,这些关键字都是被保留给 Rust 语言使用的,因此,它们不能被用作变量或函数的名称。在[附录 A](../appendix/keywords) 中可找到关键字列表。
## 变量绑定 ## 变量绑定
在其它语言中,我们用`var a = "hello world"`的方式给a赋值也就是把等式右边的`"hello world`"字符串赋值给变量`a`而在Rust中我们这样写`let a = "hello world"`, 同时给这个过程起了另一个名字:**变量绑定**。 在其它语言中,我们用 `var a = "hello world"` 的方式给 `a` 赋值,也就是把等式右边的 `"hello world"` 字符串赋值给变量 `a` ,而在 Rust 中,我们这样写: `let a = "hello world"` ,同时给这个过程起了另一个名字:**变量绑定**。
为何不用赋值而用绑定呢(其实你也可以称之为赋值,但是绑定的含义更清晰准确)?这里就涉及Rust最核心的原则-**所有权**,简单来讲,任何内存对象都是有主人的,而且一般情况下完全属于它的主人,绑定就是把这个对象绑定给一个变量,让这个变量成为它的主人(聪明的读者应该能猜到,在这种情况下,该对象之前的主人就会丧失对该对象的所有权),像极了我们的现实世界,不是吗? 为何不用赋值而用绑定呢(其实你也可以称之为赋值,但是绑定的含义更清晰准确)?这里就涉及 Rust 最核心的原则——**所有权**,简单来讲,任何内存对象都是有主人的,而且一般情况下完全属于它的主人,绑定就是把这个对象绑定给一个变量,让这个变量成为它的主人(聪明的读者应该能猜到,在这种情况下,该对象之前的主人就会丧失对该对象的所有权),像极了我们的现实世界,不是吗?
至于为何要采用所有权这种复杂的东东,先别急,等时机合适,我们会为你详细道来。 至于为何要采用所有权这种复杂的东东,先别急,等时机合适,我们会为你详细道来。
## 变量可变性 ## 变量可变性
Rust的变量在默认情况下是**不可变的**。在上文提到过这是Rust团队为我们精心设计的语言特性之一这样可以让我们编写更安全、更高性能的代码。当然你可以通过`mut`关键字让变量变为**可变的**,以实现更加灵活的设计。 Rust 的变量在默认情况下是**不可变的**。在上文提到过,这是 Rust 团队为我们精心设计的语言特性之一,这样可以让我们编写更安全、更高性能的代码。当然你可以通过 `mut` 关键字让变量变为**可变的**,以实现更加灵活的设计。
当变量不可变时,这意味着一旦一个值绑定到一个变量`a`后,就不能再更改`a`的值了。为了说明,在我们的工程目录下使用 `cargo new variables`来创建一个名为 *variables* 的新项目。 当变量不可变时,这意味着一旦一个值绑定到一个变量 `a` 后,就不能再更改 `a` 的值了。为了说明,在我们的工程目录下使用 `cargo new variables` 来创建一个名为 *variables* 的新项目。
然后在新建的 *variables* 目录下,打开 *src/main.rs* 并将代码替换为下面还未能通过编译的代码: 然后在新建的 *variables* 目录下,打开 *src/main.rs* 并将代码替换为下面还未能通过编译的代码:
@ -41,7 +41,7 @@ fn main() {
} }
``` ```
保存文件,并使用 `cargo run`运行程序或者。你将会收到一条错误消息,输出如下所示: 保存文件,并使用 `cargo run` 运行程序,你将会收到一条错误消息,输出如下所示:
```console ```console
$ cargo run $ cargo run
@ -67,9 +67,9 @@ error: aborting due to previous error
这种规则让我们的代码变得非常清晰,只有你想让你的变量改变时,它才能改变,这样就不会造成心智上的负担,也给别人阅读代码带来便利。 这种规则让我们的代码变得非常清晰,只有你想让你的变量改变时,它才能改变,这样就不会造成心智上的负担,也给别人阅读代码带来便利。
但是可变性也非常重要,否则我们就要像ClosureScript中那样每次要改变就是重新生成一个对象,在拥有大量对象的场景,性能会变得非常低下,内存拷贝的成本异常的高。 但是可变性也非常重要,否则我们就要像 ClosureScript 那样,每次要改变,就要重新生成一个对象,在拥有大量对象的场景,性能会变得非常低下,内存拷贝的成本异常的高。
在Rust中可变性很简单只要在变量名前加一个`mut`即可, 而且这种显式的声明方式还会给后来人传达这样的信息:嗯,这个变量在后面代码部分会发生改变。 Rust 中,可变性很简单,只要在变量名前加一个 `mut` 即可, 而且这种显式的声明方式还会给后来人传达这样的信息:嗯,这个变量在后面代码部分会发生改变。
为了让变量声明为可变,将 *src/main.rs* 改为以下内容: 为了让变量声明为可变,将 *src/main.rs* 改为以下内容:
@ -99,7 +99,7 @@ The value of x is: 6
### 变量解构 ### 变量解构
let表达式不仅仅用于变量的绑定还能进行复杂变量的解构从一个相对复杂的变量中匹配出该变量的一部分内容. `let` 表达式不仅仅用于变量的绑定,还能进行复杂变量的解构:从一个相对复杂的变量中,匹配出该变量的一部分内容
```rust ```rust
fn main() { fn main() {
@ -115,11 +115,12 @@ fn main() {
### 变量和常量之间的差异 ### 变量和常量之间的差异
变量的值不能更改可能让你想起其他另一个很多语言都有的编程概念:**常量***constant*)。与不可变变量一样,常量也是绑定到一个常量名且不允许更改的值,但是常量和变量之间存在一些差异 变量的值不能更改可能让你想起其他另一个很多语言都有的编程概念:**常量***constant*)。与不可变变量一样,常量也是绑定到一个常量名且不允许更改的值,但是常量和变量之间存在一些差异
首先,常量不允许使用 `mut`。**常量不仅仅默认不可变,而且自始至终不可变**。 - 常量不允许使用 `mut`。**常量不仅仅默认不可变,而且自始至终不可变**。
- 常量使用 `const` 关键字而不是 `let` 关键字来声明,并且值的类型**必须**标注。
常量使用 `const` 关键字而不是 `let` 关键字来声明,并且值的类型**必须**标注。我们将在下一节[数据类型](./base-type/index.md)中介绍,因此现在暂时无需关心细节。 我们将在下一节[数据类型](./base-type/index.md)中介绍,因此现在暂时无需关心细节。
下面是一个常量声明的例子,其常量名为 `MAX_POINTS`,值设置为 `100,000`Rust 常量的命名约定是全部字母都使用大写,并使用下划线分隔单词,另外对数字字面量可插入下划线以提高可读性): 下面是一个常量声明的例子,其常量名为 `MAX_POINTS`,值设置为 `100,000`Rust 常量的命名约定是全部字母都使用大写,并使用下划线分隔单词,另外对数字字面量可插入下划线以提高可读性):
@ -134,7 +135,7 @@ const MAX_POINTS: u32 = 100_000;
### 变量遮蔽(shadowing) ### 变量遮蔽(shadowing)
Rust允许声明相同的变量名,在后面声明的变量会遮蔽掉前面声明的, 如下所示: Rust 允许声明相同的变量名,在后面声明的变量会遮蔽掉前面声明的,如下所示:
```rust ```rust
fn main() { fn main() {
@ -162,10 +163,10 @@ The value of x in the inner scope is: 12
The value of x is: 6 The value of x is: 6
``` ```
这和`mut`变量的使用是不同的第二个let生成了完全不同的新变量两个变量只是恰好拥有同样的名称涉及一次内存对象的再分配 这和 `mut` 变量的使用是不同的,第二个 `let` 生成了完全不同的新变量,两个变量只是恰好拥有同样的名称,涉及一次内存对象的再分配
,而`mut`声明的变量,可以修改同一个内存地址上的值,并不会发生内存对象的再分配,性能要更好。 ,而 `mut` 声明的变量,可以修改同一个内存地址上的值,并不会发生内存对象的再分配,性能要更好。
变量遮蔽的用处在于,如果你在某个作用域内无需再使用之前的变量(在被遮蔽后,无法再访问到之前的同名变量),就可以重复的使用变量名字,而不用绞尽脑汁去想更多的名字。 变量遮蔽的用处在于,如果你在某个作用域内无需再使用之前的变量(在被遮蔽后,无法再访问到之前的同名变量),就可以重复的使用变量名字,而不用绞尽脑汁去想更多的名字。
例如,假设有一个程序要统计一个空格字符串的空格数量: 例如,假设有一个程序要统计一个空格字符串的空格数量:
```rust ```rust
@ -175,7 +176,7 @@ let spaces = " ";
let spaces = spaces.len(); let spaces = spaces.len();
``` ```
这种结构是允许的,因为第一个 `spaces` 变量是一个字符串类型,第二个 `spaces` 变量是一个全新的变量且和第一个具有相同的变量名,且是一个数值类型。所以变量遮蔽可以帮我们节省些脑细胞,不用去想如`spaces_str` 和 `spaces_num`此类的变量名;相反我们可以重复使用更简单的 `spaces` 变量名。 如果你不用`let`: 这种结构是允许的,因为第一个 `spaces` 变量是一个字符串类型,第二个 `spaces` 变量是一个全新的变量且和第一个具有相同的变量名,且是一个数值类型。所以变量遮蔽可以帮我们节省些脑细胞,不用去想如 `spaces_str``spaces_num` 此类的变量名;相反我们可以重复使用更简单的 `spaces` 变量名。如果你不用 `let` :
```rust, ```rust,
let mut spaces = " "; let mut spaces = " ";
@ -196,6 +197,6 @@ error[E0308]: mismatched types
error: aborting due to previous error error: aborting due to previous error
``` ```
显然Rust对类型的要求很严格不允许将整数类型`usize`赋值给字符串类型,`usize`是一种cpu相关的整数类型在[数值类型](./base-type/numbers#整数类型)有详细介绍. 显然Rust 对类型的要求很严格,不允许将整数类型 `usize` 赋值给字符串类型。`usize` 是一种 CPU 相关的整数类型,在[数值类型](./base-type/numbers#整数类型)中有详细介绍。
万事开头难到目前为止都进展很顺利那下面开始咱们正式进入Rust的类型世界看看有哪些挑战在前面等着大家。 万事开头难,到目前为止,都进展很顺利,那下面开始,咱们正式进入 Rust 的类型世界,看看有哪些挑战在前面等着大家。

@ -97,7 +97,7 @@ $ cargo check
Finished dev [unoptimized + debuginfo] target(s) in 0.06s Finished dev [unoptimized + debuginfo] target(s) in 0.06s
``` ```
> Rust 虽然编译速度还行,但是还是不能 Go语言相提并论因为 Rust 需要做很多复杂的编译优化和语言特性解析, 甚至连如何优化编译速度都成了一门学问[优化编译速度](../compiler/speed-up.md) > Rust 虽然编译速度还行,但是还是不能 Go 语言相提并论,因为 Rust 需要做很多复杂的编译优化和语言特性解析, 甚至连如何优化编译速度都成了一门学问[优化编译速度](../compiler/speed-up.md)
## Cargo.toml 和 Cargo.lock ## Cargo.toml 和 Cargo.lock
@ -152,4 +152,4 @@ geometry = { path = "crates/geometry" }
前文有提到 `cargo` 默认生成的项目结构,但是真实的项目还有所不同,但是在目前的学习阶段,还无需关注,感兴趣的同学可以移步:[Cargo项目结构](../cargo/layout.md) 前文有提到 `cargo` 默认生成的项目结构,但是真实的项目还有所不同,但是在目前的学习阶段,还无需关注,感兴趣的同学可以移步:[Cargo项目结构](../cargo/layout.md)
至此,大家对 Rust 项目的创建和管理已经有了初步的了解,那么来完善刚才的`"世界,你好"`项目吧。 至此,大家对 Rust 项目的创建和管理已经有了初步的了解,那么来完善刚才的 `"世界,你好"` 项目吧。

@ -14,7 +14,7 @@
## 安装 VSCode 的 Rust 插件 ## 安装 VSCode 的 Rust 插件
在 VScode 的左侧扩展目录里,搜索 `rust`, 你能看到两个 Rust 插件,如果没有意外,这两个应该分别排名第一和第二: 在 VSCode 的左侧扩展目录里,搜索 `rust`, 你能看到两个 Rust 插件,如果没有意外,这两个应该分别排名第一和第二:
1. 官方的 `Rust`,作者是 `The Rust Programming Language`, 官方出品,牛逼就完了,但是……我们并不推荐,这个插件有几个问题: 1. 官方的 `Rust`,作者是 `The Rust Programming Language`, 官方出品,牛逼就完了,但是……我们并不推荐,这个插件有几个问题:
- 首先是在代码跳转上支持的很烂,只能在自己的代码库中跳转,一旦跳到别的三方库,那就无法继续跳转,对于查看标准库和三方库的源码带来了极大的困扰 - 首先是在代码跳转上支持的很烂,只能在自己的代码库中跳转,一旦跳到别的三方库,那就无法继续跳转,对于查看标准库和三方库的源码带来了极大的困扰
- 其次,不支持类型自动标注,对于 Rust 语言而言,类型说明是非常重要的,特别是在你不知道给变量一个什么类型时,这种 IDE 的自动提示就变得弥足珍贵 - 其次,不支持类型自动标注,对于 Rust 语言而言,类型说明是非常重要的,特别是在你不知道给变量一个什么类型时,这种 IDE 的自动提示就变得弥足珍贵

@ -4,7 +4,7 @@
## 多国语言的"世界,你好" ## 多国语言的"世界,你好"
还记得大明湖畔等你的[VSCode](./editor.md) IDE 和通过 `Cargo` 创建的[世界,你好](./cargo.md)工程嘛? 还记得大明湖畔等你的 [VSCode](./editor.md) IDE 和通过 `Cargo` 创建的[世界,你好](./cargo.md)工程嘛?
现在使用 VSCode 打开[上一节](./cargo.md)中创建的 `world_hello` 工程, 然后进入 `main.rs` 文件,此文件是当前 Rust 工程的入口文件,和其它语言几无区别。 现在使用 VSCode 打开[上一节](./cargo.md)中创建的 `world_hello` 工程, 然后进入 `main.rs` 文件,此文件是当前 Rust 工程的入口文件,和其它语言几无区别。

@ -2,7 +2,7 @@
## Rust 发展历程 ## Rust 发展历程
Rust 最早是 Mozilla 雇员 Graydon Hoare 的个人项目。从 2009 年开始,得到了 Mozilla 研究院的资助。2010 年项目对外公布2010 2011 年间实现自举。自此以后Rust 在设计变化->崩溃的边缘反复横跳(历程极其艰辛)。终于,在 2015 年 5 月 15 日发布 1.0 版。 Rust 最早是 Mozilla 雇员 Graydon Hoare 的个人项目。从 2009 年开始,得到了 Mozilla 研究院的资助。2010 年项目对外公布2010 2011 年间实现自举。自此以后Rust 在设计变化 -> 崩溃的边缘反复横跳(历程极其艰辛)。终于,在 2015 年 5 月 15 日发布 1.0 版。
在此研发过程中Rust 建立了一个强大且活跃社区形成了一整套完善稳定的项目贡献机制Rust 能够飞速发展与这一点密不可分。Rust 现在由 [Rust 项目开发者社区](https://github.com/rust-lang/rust) 维护。 在此研发过程中Rust 建立了一个强大且活跃社区形成了一整套完善稳定的项目贡献机制Rust 能够飞速发展与这一点密不可分。Rust 现在由 [Rust 项目开发者社区](https://github.com/rust-lang/rust) 维护。
@ -91,7 +91,7 @@ Rust 的开发效率可以用先抑后扬来形容。在刚开始上手写项目
目前 Rust 的主战场是在开源上Go 的成功也证明了农村包围城市的可行性。 目前 Rust 的主战场是在开源上Go 的成功也证明了农村包围城市的可行性。
- UI 层开发Rust 的 WASM 发展的如火如荼,隐隐有王者风范,在 JS 的基础设施领域Rust 也是如鱼得水,例如`swc`、`deno`等。同时`nextjs`也是押宝 Rust可以说 Rust 在前端的成功完全是无心插柳柳成荫。 - UI 层开发Rust 的 WASM 发展的如火如荼,隐隐有王者风范,在 JS 的基础设施领域Rust 也是如鱼得水,例如 `swc` `deno` 等。同时 `nextjs` 也是押宝 Rust可以说 Rust 在前端的成功完全是无心插柳柳成荫。
- 基础设施层,数据库、搜索引擎、网络设施、云原生等都在出现 Rust 的身影,而且还不少。 - 基础设施层,数据库、搜索引擎、网络设施、云原生等都在出现 Rust 的身影,而且还不少。
- 系统开发,目前 Linux 已经将 Rust 列为即将支持的内核开发语言,是继 C 语言后第二门支持内核开发的语言,不过刚开始将主要支持驱动开发。 - 系统开发,目前 Linux 已经将 Rust 列为即将支持的内核开发语言,是继 C 语言后第二门支持内核开发的语言,不过刚开始将主要支持驱动开发。
- 系统工具,现在最流行的就是用 Rust 重写之前 C、C++ 写的一票系统工具,还都获得了挺高的关注和很好的效果,例如 sd, exa, ripgrep, fd, bat 等。 - 系统工具,现在最流行的就是用 Rust 重写之前 C、C++ 写的一票系统工具,还都获得了挺高的关注和很好的效果,例如 sd, exa, ripgrep, fd, bat 等。
@ -149,7 +149,7 @@ Rust 语言表达能力更强,性能更高。同时线程安全方面 Rust 也
- 对于一般的用户edition 大版本的发布会告诉他们Rust 语言相比上次大版本发布,有了重大的改进,值得一看 - 对于一般的用户edition 大版本的发布会告诉他们Rust 语言相比上次大版本发布,有了重大的改进,值得一看
- 对于 Rust 语言开发者,可以让他们的工作成果更快的被世人所知,不必锦衣夜行 - 对于 Rust 语言开发者,可以让他们的工作成果更快的被世人所知,不必锦衣夜行
好了,相信大家听了这么多 Rust 的优点,已经迫不及待想要开始学习旅程,那么容我引用一句 CS 的经典台词OK, let's go. 好了,相信大家听了这么多 Rust 的优点,已经迫不及待想要开始学习旅程,那么容我引用一句 CS 的经典台词Ok, Let's go.
## 总结 ## 总结

Loading…
Cancel
Save