Refining Ch03.

This commit is contained in:
rust-lang.xfoss.com 2023-12-08 10:54:04 +08:00
parent 4965f88387
commit 1e2d49aeb6
2 changed files with 96 additions and 43 deletions

View File

@ -1,7 +1,4 @@
fn main() {
let x = 5;
println! ("x 的值为:{x}");
x = 6;
println! ("x 的值为:{x}");
let mut spaces = " ";
spaces = spaces.len();
}

View File

@ -46,14 +46,16 @@ error: could not compile `variables` due to previous error
咱们之所以收到了错误消息 `cannot assing twice to immutable variable 'x'`,是因为咱们试图将第二个值,赋值给那个不可变的变量 `x`
在尝试修改某个被指定为不可变的值时,由于这种情况会导致程序错误,因此这个时候收到编译时错误尤为重要。代码一部分的运作,是建立在值将绝不会改变这种假定上,而代码另一部分却修改了那个值,那么就有可能代码的第一部分未有完成他预计要完成的工作了。此类程序错误的原因,就难于追踪到真相,尤其是在代码第二部分仅 *有的时候* 才对那个值进行修改时。Rust 编译器保证了在表明某个值不会变化时,那么那个值就真的不会变化,如此就不必亲自去紧盯着那个变量了。代码也由此而更易于推演
重要的是,当我们试图更改某个被指定为不可变的值时,会出现编译时错误,因为这种情况可能会导致错误。如果我们代码的一部分假设某个值永远不会改变,而代码的另一部分却改变了该值,那么代码的前一部分就有可能无法完成其设计目标。这种错误的原因可能很难事后追踪,尤其是当第二部分代码只是 *偶尔* 改变值时。Rust 编译器保证,当咱们声明了某个值不会改变时,他确实不会改变,因此咱们不必自己跟踪它。这样,咱们的代码就更容易推理了
然而可变则可能会非常有用,并能令到代码更便于编写。变量仅在默认情况下是不可变的;就如同在第 2 章中所做的那样,可通过在变量名字前添加 `mut` 关键字,让变量成为可变。`mut` 的添加,也向将来代码的读者传达了某种意图,表示代码的其他部分,会对这个变量的值进行修改
但可变会非常有用,可以让代码编写更方便。虽然变量在默认情况下是不可变的,但也可以像 [第 2 章](../Ch02_Programming_a_Guessing_Game.md#使用变量存储值) 中咱们曾做的那样,在变量名前添加 `mut`,使其成为可变变量。添加 `mut` 还可以向代码的未来读者,传达某种意图,即代码的其他部分,将改变该变量的值
比如,来将 `src/main.rs` 修改为下面这样:
比如,咱们来将 `src/main.rs` 修改为下面这样:
文件名:`src/main.rs`
```rust
fn main() {
let mut x = 5;
println! ("x 的值为:{}", x);
@ -62,44 +64,86 @@ error: could not compile `variables` due to previous error
}
```
在此时运行这个程序时,就会得到这样的输出:
当我们现在运行这个程序时,会得到这样的结果:
```rust
$ cargo run  101 ✘
Compiling variables v0.1.0 (/home/peng/rust-lang/projects/variables)
Finished dev [unoptimized + debuginfo] target(s) in 0.46s
Running `target/debug/variables`
$ cargo run
Compiling variables v0.1.0 (C:\tools\msys64\home\Lenny.Peng\rust-lang-zh_CN\projects\variables)
Finished dev [unoptimized + debuginfo] target(s) in 1.26s
Running `target\debug\variables.exe`
x 的值为5
x 的值为6
```
在使用了 `mut` 关键字时,就被允许将绑定到 `x` 的值从 `5` 修改到 `6` 了。除了防止程序错误之外还要考虑多种权衡。比如在使用着大型数据结构时就地修改其的一个实例就会比拷贝并返回新近分配的实例要快一些for example, in cases where you're using large data structures, mutating an instance in place may be faster than copying and returning newly allocated instances。而对于较小的数据结构创建其新实例并以更具函数式编程风格来编写代码则可能更易于理解那么由此带来的性能下降相对所取得的思路清晰也会是值得的。
在使用了 `mut` 时,我们就可以将绑定到 `x` 的值,从 `5` 改为 `6`。最终,是否使用可变,取决于咱们自己,取决于咱们认为在特定情况下,什么是最清晰的。
## 常量
与不可变变量类似, *常量constants* 是一些绑定到名字且不允许修改的值,但常量与变量之间,有些差异。
**Constants**
首先,是不允许在常量上使用 `mut` 关键字的。常量不光是默认不可变的 -- 他们一直都是不可变的。常量的声明用的是 `const` 关键字,而不是 `let` 关键字,同时值的类型 *必须* 被注解be annotated。在下一小节[数据类型](#数据类型),就会讲到类型和类型注解了,因此现在不要关心细节。只要明白必须始终对类型进行注解。
可在任何作用域,包括全局作用域中声明常量。而当在全局作用域中声明常量时,则会让那些代码中许多部分都需要知悉的值的常量,变得有用起来
与不可变的变量一样,*常量constants* 也是绑定到名字,且不允许更改的值,但常量和变量之间有些区别
常量与不可变变量的最后一个区别,就是常量只能被设置到一个常量表达式,而不能被设置为只能在运行时计算出结果的值
首先,不允许将 `mut` 与常量一起使用。常量不仅默认是不可变的,而且始终不可变。咱们要使用 `const` 关键字,而不是 `let` 关键字,声明常量,而且 *必须* 注解值的类型。我们将在下一小节 [“数据类型”](data_types.md) 中,介绍类型和类型注解,所以现在不用担心这些细节。只需知道咱们必须始终注解类型即可
下面是一个常量声明的示例:
常量可以在任何作用域(包括全局作用域)中声明,因此对于代码的多个部分都需要知道的那些值来说,常量非常有用。
常量与不可变变量的最后一个区别是常量只能设置为一个常量表达式而不是只能在运行时计算出的值的结果constants may be set only to a constant expression, not the result of a value that could only be computed at runtime。
> **译注**:这句话的意思,常量的初始化赋值表达式不能包含变量。如下面的代码:
```rust
fn main() {
let a = 3;
const THREE_HOURS_IN_SECONDS: u32 = a * 60 * 60;
println! ("三个小时的秒数:{THREE_HOURS_IN_SECONDS}");
}
```
> 将报错:
```console
$ cargo run
Compiling variables v0.1.0 (C:\tools\msys64\home\Lenny.Peng\rust-lang-zh_CN\projects\variables)
error[E0435]: attempt to use a non-constant value in a constant
--> src\main.rs:4:41
|
4 | const THREE_HOURS_IN_SECONDS: u32 = a * 60 * 60;
| ---------------------------- ^ non-constant value
| |
| help: consider using `let` instead of `const`: `let THREE_HOURS_IN_SECONDS`
For more information about this error, try `rustc --explain E0435`.
error: could not compile `variables` (bin "variables") due to previous error
```
下面是个常量声明的示例:
```rust
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
```
该常量的名字为 `THREE_HOURS_IN_SECONDS`,而他的值就被设置为了 `60` (即一分钟的秒数)乘以 `60` (即一小时的分钟数)乘以 `3` 此程序中要计数的小时数。Rust 关于常量命名的约定,即为全部使用大写,在词汇之间用下划线隔开。编译器在运行时,能够执行一套受限的运算,这样就可以选择将常量值,以这种更易于理解和验证的方式写出来,而不是将该常量设置为值 `10,800`。请参阅 [Rust 参考手册有关常量求值的小节](https://doc.rust-lang.org/reference/const_eval.html),了解更多有关在声明常量时可使用那些运算的信息。
这个常量的名字是 `THREE_HOURS_IN_SECONDS`,其值被设置为 60一分钟的秒数乘以 60一小时的分钟数再乘以 3本程序中要计算的小时数的结果。Rust 的常量命名约定,是使用全大写字母,单词之间使用下划线。编译器可以在编译时,计算一个有限的运算集,这让我们可以选择,以一种更容易理解和验证的方式写出这个值,而不是将这个常量设置为值 10,800。有关在声明常量时可以使用哪些运算的更多信息请参阅 [Rust 参考手册中,关于常量求值的小节](https://doc.rust-lang.org/reference/const_eval.html)。
常量在程序运行的全部时间、在其被声明的作用域内部,都是有效的。常量的这个属性,令到常量对于应用域内的那些、程序多个部分都需要知悉的值来说,变得有用起来,比如某个游戏全部玩家所允许赚到的最大点数,或光速常量。
常量在程序运行的整个过程中,在其声明的范围内都有效。这一属性使得常量对于程序域中,多个部分可能需要了解的值,例如游戏中允许任何玩家获得的最大点数或光速,会非常有用
对那些整个程序都要用到的、作为常量的硬编码值进行取名,对于向代码将来的维护者们传达那些值的意义,是相当有用的。对于未来需要更新硬编码值来说,对常量命名就让那些需要修改的代码只有一处要改,而对此带来帮助。
将整个程序中要用到的一些值,命名为常量,有助于向未来的代码维护者,传达该值的含义。此外,如果将来需要更新硬编码值,只需更改咱们代码中的一处即可
## 遮蔽shadowing
如同在第 2 章中的猜数游戏里看到的那样可声明一个与先前变量同名的新变量。Rust 公民们表示,那第一个变量是被第二个给 *遮蔽* 掉了,这就意味着在用到这个变量是,程序所看到的,会是第二个变量的值。通过使用一样的变量名字,以及重复使用 `let` 关键字,就可对某个变量进行遮蔽,如下所示:
## 遮蔽
**Shadowing**
如同咱们曾在 [第 2 章](../Ch02_Programming_a_Guessing_Game.md#将猜数与秘数相比较) 的猜数游戏教程中所看到的咱们可以声明出一个与先前的某个变量同名的新变量。Rustaceans 会说,第一个变量是被第二个 *遮蔽了shadowd*,这意味着当你使用这个变量的名字时,编译器将看到的,是第二个变量。实际上,第二个变量覆盖了第一个,将变量名的任何使用,都带到自己身上,直到他自己被遮蔽,或作用域结束。像下面这样,通过使用相同的变量名,和重复使用 `let` 关键字,咱们便可以遮蔽某个变量:
文件名:`src/main.rs`
@ -118,6 +162,7 @@ fn main() {
}
```
```console
内部作用域中 x 的值为12
x 的值为6
@ -125,49 +170,60 @@ x 的值为6
> 注意:遮蔽特性的使用,不需要 `mut` 关键字。
这个程序首先将 `x` 绑定到值 `5`。随后通过重复 `let x =`,取原来的值并加上 `1`,而对 `x` 做了遮蔽操作,因此 `x` 的值此时就为 `6` 了。之后,在一个内部作用域内,第三个 `let` 语句也对 `x` 进行了遮蔽,将先前的值乘以 `2`,就给到 `x` 一个值 `12`。在那个内部作用域完毕时,那个内部遮蔽就结束了,同时 `x` 回到仍为 `6`。在运行这个程序时,他将输出下面的内容:
这个程序首先将 `x`,绑定到 5 的值。然后通过重复 `let x =`,创建出一个新变量 `x`,将原来的值加上 `1`,这样 `x` 的值就是 `6`。然后,在用大括号创建的一个内层作用域中,第三个 `let` 语句也对 `x` 进行了遮蔽处理,并创建了一个新变量,将先前的值乘以 `2`,使 `x` 的值为 `12`。当这个作用域结束时,内部的遮蔽结束,`x` 返回到 `6`。当我们运行这个程序时,他将输出如下内容:
```console
$ cargo run  ✔
Compiling variables v0.1.0 (/home/peng/rust-lang/projects/variables)
Finished dev [unoptimized + debuginfo] target(s) in 0.47s
Running `target/debug/variables`
$ cargo run
Compiling variables v0.1.0 (C:\tools\msys64\home\Lenny.Peng\rust-lang-zh_CN\projects\variables)
Finished dev [unoptimized + debuginfo] target(s) in 1.03s
Running `target\debug\variables.exe`
内部作用域中 x 的值为12
x 的值为6
```
遮蔽操作不同于将变量构造为 `mut`,因为如果我们不小心在没有使用 `let` 关键字的情况下,将变量重新赋值,咱们就会得到一个编译时报错。而通过使用 `let` 关键字,我们可以对某个值执行一些变换,但在这些变换完成后,该变量将不可变。
由于在不小心而尝试在不带 `let` 关键字而重新赋值给该变量时,会收到编译时错误,因此遮蔽不同于构造一个`mut` 的变量。通过使用 `let` 关键字,就可以在值上执行少量的转换操作,而在这些转换操作完成后又将该变量置入到不可变。
`mut` 与遮蔽的另一个区别是,当我们再次使用 `let` 关键字时,我们实际上是创建了一个新变量,因此我们可以改变该值的类型,而可以重复使用这个同样的名字。例如,假设咱们的程序要求用户,通过输入一些空格字符,来给出他们想要的某些文本之间多少个空格,然后我们打算将这个输入,存储为一个数字:
`mut` 与遮蔽的另一不同之处,则是由于再次使用`let`关键字时,有效地创建出了一个新变量,因此就可以改变那个值的类型,而仍然重用那同样的变量名字。比如说程序要通过用户输入若干空格字符,来询问用户希望在一些文本之间留多少个空格,而此时又要将用户输入的若干个空格,保存为一个数字:
```rust
let spaces = " ";
let spaces = spaces.len();
```
第一个 `spaces` 变量是字符串类型,而第二个 `spaces` 变量则是数字类型。遮蔽因此而免于不得不苦苦思索不同的变量名字,诸如 `spaces_str``spaces_num`;相反,是可以重新较简单的 `spaces` 名称。然而,若尝试对这个变量使用 `mut` 关键字,就会收到一个编译时错误,如下所示:
第一个 `spaces` 变量,属于字符串类型,而第二个 `spaces` 变量,则是数字类型。遮蔽就这样,让我们不必使用不同的名称,如 `spaces_str``spaces_num`;相反,我们可以重复使用这个更简单的 `spaces` 名称。然而,如果我们尝试使用 `mut` 来实现这一点,如下所示,咱们就会收到一个编译时报错:
```rust
let mut spaces = " ";
spaces = spaces.len();
```
错误是说不允许转变变量类型:
该报错表明,我们不允许改变变量的类型:
```console
$ cargo run  ✔
Compiling variables v0.1.0 (/home/peng/rust-lang/projects/variables)
$ cargo run
Compiling variables v0.1.0 (C:\tools\msys64\home\Lenny.Peng\rust-lang-zh_CN\projects\variables)
error[E0308]: mismatched types
--> src/main.rs:14:14
|
13 | let mut spaces = " ";
| ------ expected due to this value
14 | spaces = spaces.len();
| ^^^^^^^^^^^^ expected `&str`, found `usize`
--> src\main.rs:3:14
|
2 | let mut spaces = " ";
| ------ expected due to this value
3 | spaces = spaces.len();
| ^^^^^^^^^^^^ expected `&str`, found `usize`
|
help: try removing the method call
|
3 - spaces = spaces.len();
3 + spaces = spaces;
|
For more information about this error, try `rustc --explain E0308`.
error: could not compile `variables` due to previous error
error: could not compile `variables` (bin "variables") due to previous error
```
现在已经完成变量运行机制的探讨,接卸来就要看看这些变量可以有的那些其余数据类型了。
现在我们已经探讨了变量的工作原理,我们来看看,他们可以拥有的更多数据类型。