and file and code

main
18522150130 1 year ago
parent 98c122026f
commit b46574738d
  1. 2
      .gitignore
  2. 99
      md_file/1.md
  3. 221
      md_file/10.1.md
  4. 260
      md_file/10.2.md
  5. 129
      md_file/10.md
  6. 153
      md_file/11(1).md
  7. 125
      md_file/11.3.md
  8. 354
      md_file/11.md
  9. 348
      md_file/12.md
  10. 426
      md_file/13 函数式语言特性迭代器和闭包.md
  11. 624
      md_file/14 关于 Cargo 和 Crates.io 的更多内容.md
  12. 1185
      md_file/15 智能指针.md
  13. 93
      md_file/1590. 使数组和能被 P 整除.md
  14. 153
      md_file/1605. 给定行和列的和求可行矩阵.md
  15. 172
      md_file/1615. 最大网络秩.md
  16. 90
      md_file/1626. 无矛盾的最佳球队.md
  17. 558
      md_file/16并发.md
  18. 730
      md_file/17oop.md
  19. 889
      md_file/18.md
  20. 904
      md_file/19.md
  21. 183
      md_file/2.md
  22. 438
      md_file/20.md
  23. 81
      md_file/2379. 得到 K 个黑块的最少涂色次数.md
  24. 242
      md_file/2383. 赢得比赛需要的最少训练时长.md
  25. 103
      md_file/2389. 和有限的最长子序列.md
  26. 122
      md_file/2488. 统计中位数为 K 的子数组.md
  27. 79
      md_file/3.1.md
  28. 204
      md_file/3.md
  29. 108
      md_file/3练习.md
  30. 90
      md_file/4.1.md
  31. 220
      md_file/4.2.md
  32. 145
      md_file/4.md
  33. 78
      md_file/5.1.md
  34. 67
      md_file/5.2.md
  35. 67
      md_file/5.md
  36. 63
      md_file/6.1.md
  37. 64
      md_file/6.2.md
  38. 31
      md_file/6.3let 和 match.md
  39. 135
      md_file/6.md
  40. 58
      md_file/7.1.md
  41. 187
      md_file/7.2.md
  42. 184
      md_file/7.md
  43. 176
      md_file/8.1.md
  44. 378
      md_file/8.md
  45. 113
      md_file/9.1.md
  46. 169
      md_file/9.md
  47. 410
      md_file/Note Functions can implement all three of the Fn traits too.md
  48. 7
      md_file/temp.md
  49. 67
      md_file/波特率.md
  50. 7
      rust_code/1-4/borrow/Cargo.lock
  51. 8
      rust_code/1-4/borrow/Cargo.toml
  52. BIN
      rust_code/1-4/borrow/src/.main.rs.swp
  53. 10
      rust_code/1-4/borrow/src/main.rs
  54. 123
      rust_code/1-4/chapter/6/option/.idea/workspace.xml
  55. 7
      rust_code/1-4/dangling/Cargo.lock
  56. 8
      rust_code/1-4/dangling/Cargo.toml
  57. 13
      rust_code/1-4/dangling/src/main.rs
  58. 1
      rust_code/1-4/fahrenheit_and_celsius/.gitignore
  59. 2
      rust_code/1-4/fahrenheit_and_celsius/.idea/.gitignore
  60. 8
      rust_code/1-4/fahrenheit_and_celsius/.idea/fahrenheit_and_celsius.iml
  61. 6
      rust_code/1-4/fahrenheit_and_celsius/.idea/misc.xml
  62. 8
      rust_code/1-4/fahrenheit_and_celsius/.idea/modules.xml
  63. 6
      rust_code/1-4/fahrenheit_and_celsius/.idea/vcs.xml
  64. 7
      rust_code/1-4/fahrenheit_and_celsius/Cargo.lock
  65. 8
      rust_code/1-4/fahrenheit_and_celsius/Cargo.toml
  66. 46
      rust_code/1-4/fahrenheit_and_celsius/src/main.rs
  67. 1
      rust_code/1-4/fib/.gitignore
  68. 2
      rust_code/1-4/fib/.idea/.gitignore
  69. 11
      rust_code/1-4/fib/.idea/fib.iml
  70. 6
      rust_code/1-4/fib/.idea/misc.xml
  71. 8
      rust_code/1-4/fib/.idea/modules.xml
  72. 6
      rust_code/1-4/fib/.idea/vcs.xml
  73. 7
      rust_code/1-4/fib/Cargo.lock
  74. 8
      rust_code/1-4/fib/Cargo.toml
  75. BIN
      rust_code/1-4/fib/src/.main.rs.swl
  76. BIN
      rust_code/1-4/fib/src/.main.rs.swm
  77. BIN
      rust_code/1-4/fib/src/.main.rs.swn
  78. BIN
      rust_code/1-4/fib/src/.main.rs.swo
  79. BIN
      rust_code/1-4/fib/src/.main.rs.swp
  80. 21
      rust_code/1-4/fib/src/main.rs
  81. 1
      rust_code/1-4/guessing_game/.gitignore
  82. 75
      rust_code/1-4/guessing_game/Cargo.lock
  83. 9
      rust_code/1-4/guessing_game/Cargo.toml
  84. BIN
      rust_code/1-4/guessing_game/src/.main.rs.swp
  85. 35
      rust_code/1-4/guessing_game/src/main.rs
  86. 7
      rust_code/1-4/new_first_word/Cargo.lock
  87. 8
      rust_code/1-4/new_first_word/Cargo.toml
  88. 34
      rust_code/1-4/new_first_word/src/main.rs
  89. 7
      rust_code/1-4/other_slice/Cargo.lock
  90. 8
      rust_code/1-4/other_slice/Cargo.toml
  91. 5
      rust_code/1-4/other_slice/src/main.rs
  92. 1
      rust_code/1-4/overflow/.gitignore
  93. 7
      rust_code/1-4/overflow/Cargo.lock
  94. 8
      rust_code/1-4/overflow/Cargo.toml
  95. 5
      rust_code/1-4/overflow/src/main.rs
  96. 1
      rust_code/1-4/ownership/.gitignore
  97. 2
      rust_code/1-4/ownership/.idea/.gitignore
  98. 6
      rust_code/1-4/ownership/.idea/misc.xml
  99. 8
      rust_code/1-4/ownership/.idea/modules.xml
  100. 8
      rust_code/1-4/ownership/.idea/ownership.iml
  101. Some files were not shown because too many files have changed in this diff Show More

2
.gitignore vendored

@ -1,2 +1,2 @@
# 屏蔽 Rust 项目中的 target 目录
/target/
**/target/

@ -0,0 +1,99 @@
# 1
## rust语言
```
rustup docs --book 打开本地文档
rustc --version 查看rust版本
rustup update 更新rust
rustup self uninstall 卸载rust和 rustup
cargo --version 查看cargo版本
```
安装时需要 c++ build tools !!, cargo回合rust一同安装。
## rust风格
1. Rust风格默认为四个空格缩进,而不是制表符。
1. println!调用Rust宏。若调用一个函数应输入println(不带!)。
1. 以分号结尾。
## Compiling and Running
1. 运行前需要先使用Rust编译器进行编译,方法是使用rustc命令: rustc xx.rs
2. 在linux和macOS上会生成两个文件 xx.rs、xx; 而在window下会产生三个文件,包含源代码、可执行文件(在windows上为xx.exe,在其他所有平台上都为main),以及使用windows时包含调试信息且扩展名为.pdb的文件![image-20221227111944833](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221227111944833.png)
运行方式:
![image-20221227112810717](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221227112810717.png)
## cargo
简单的程序可以使用rustc进行编译,但实际开发中,更多是使用Cargo来构建rust系统和包管理器,可以使用cargo构建代码、下载代码所依赖的库以及构建这些库(库一般称为dependencies)
### 使用cargo创建项目
```rust
$ cargo new hello_cargo
```
![image-20221227114101592](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221227114101592.png)
![image-20221227114108663](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221227114108663.png)
cargo new 如果您在现有的Git存储库中运行,则不会生成Git文件;您可以使用覆盖此行为cargo new --vcs=git。 --vcs参数
![image-20221227114619246](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221227114619246.png)
![image-20221227114807597](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221227114807597.png)
### TOML格式
TOML(Tom's Obvious, Minimal Language)格式,是Cargo的配置格式。
![image-20221227115110315](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221227115110315.png)
[package]为一个节标题,表示以下语句正在配置包,
接下来的三行设置 Cargo 编译程序所需的配置信息:名称、版本和要使用的 Rust 版本。
[dependencies]是您列出任何项目依赖项的部分的开头
rust中代码包被称为crate。此项目暂不需要其他crate
### Compiling and Running
```rust
cargo build
```
此命令在target/debug/hello_cargo(或 Windows 上的target\debug\hello_cargo.exe)而不是当前目录中创建一个可执行文件。
![](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221227134259018.png)
同时首次使用cargo build后也会在顶层创建一个新文件:Cargo.lock。该文件用于跟踪项目中依赖项的确切版本。此文件永远不需要手段更改;
```rust
cargo run
```
使用cargo run 不需要到二进制文件的整个路径,更便捷。若修改了源代码,cargo会执行一次 rebuild 操作。
![image-20221227140959687](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221227140959687.png)
```
cargo check
```
此命令快速检查您的代码以确保它可以编译但不会生成可执行文件。可以在cargo build前使用此命令,保证项目是可编译的,节省试错时间。
### Building for Release
```
Building for Release
```
*此命令将在target/release*而不是*target/debug*中创建可执行文件,用于发布项目。
![image-20221227142421493](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221227142421493.png)

@ -0,0 +1,221 @@
## Trait:定义共享行为
trait定义了特定类型所具有的**功能**,并且可以与其他类型**共享**。我们可以用特征来抽象地定义**共享行为**。我们可以使用trait边界来指定泛型类型可以是具有**特定行为**的任何类型。
注意:trait类似于其他语言中称为**接口**的特性,尽管有一些不同。
### 定义一个Trait
类型的行为由我们可以调用该类型的**方法**组成。如果我们可以对所有这些类型调用相同的方法,不同的类型就会共享相同的行为。Trait定义是一种将**方法签名**分组在一起的方法,以定义完成某些目的所必需的**一组行为**。
```rust
pub trait Summary {
fn summarize(&self) -> String;
}
```
**trait**关键字和一个名称来声明trait。我们还将这个trait声明为pub,这样依赖于这个crate的crate也可以使用这个trait,正如我们将在几个例子中看到的那样。
在方法签名之后,我们使用分号,而**不是在花括号内提供实现**。实现此特征的每个类型必须为方法体提供自己的**自定义行为**。
trait的主体中可以有**多个方法**:每行列出一个方法签名,每行以分号结束。
### 实现一个Trait
在impl之后,我们放入想要实现的trait名称,然后使用for关键字,然后指定想要为其实现trait的类型的名称。
![image-20230216100752058](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230216100752058.png)
lib位置,以及Cargo.toml中配置
![image-20230216101013803](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230216101013803.png)
### 默认实现
为trait中部分或所有方法设置默认行为,而不是要求每种类型的所有方法都实现默认行为很有用。只需实现非默认的行为。
![image-20230216135657178](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230216135657178.png)
默认方法不需要在特定结构中实现就可以使用。同时默认方法可在特定结构里重写。
![image-20230216141716908](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230216141716908.png)
### 特征作为参数
使用特征来定义接受多种不同类型的函数。
![image-20230216150138176](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230216150138176.png)
#### Trait Bound语法
```rust
pub fn notify<T: Summary>(item: &T){
println!("Breaking news! {}", item.summarize());
}
```
单参数:
![image-20230216160527803](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230216160527803.png)
多参数:
![image-20230216161450693](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230216161450693.png)
若想限制只能**同一结构**,可以只是用一个泛型参数T。
#### 使用+语法指定多个Trait Bounds
感觉就类似于实现多个接口。
普通格式:
```rust
pub fn notify(item: &(impl Summary + Display)) {
```
泛型格式(更好):
```rust
pub fn notify<T: Summary + Display>(item: &T) {
```
#### 使用where子句明确Trait Bounds
每个泛型都有自己的trait bounds,因此具有多个泛型类型参数的函数在函数名和参数列表之间可能包含大量trait bounds信息,使得函数签名难以阅读。出于这个原因,Rust在函数签名之后的where子句中使用了另一种语法来指定trait bounds。
不推荐的写法:
```rust
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
```
更推荐的写法:
```rust
fn some_function<T, U>(t: &T, u: &U) -> i32
where
T: Display + Clone,
U: Clone + Debug,
{
unimplemented!()
}
```
## 返回类型为特征
![image-20230216164105767](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230216164105767.png)
若想返回属于此特征的**单个类型**,使用if-else会产生如下错误:*`if` and `else` have incompatible types* 原因是:当 `switch` 为真时返回了一个 `NewArticle` 类型的值,当 `switch` 为假时返回了一个 `Tweet` 类型的值。由于这两个类型都实现了 `Summary` trait,因此我们可以将它们统一用 `impl Summary` 来返回。然而,由于 `NewArticle``Tweet` 的类型不同,导致了 `if``else` 两个分支的返回类型不一致,因此编译器会报错。
![image-20230216172735181](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230216172735181.png)
正确写法:返回类型改为 `Box<dyn Summary>`,其中 `dyn` 是 Rust 中表示 trait 对象的关键字。这个函数现在可以正常运行,而且它可以在运行时**动态**选择要返回的类型,这也是 trait 对象的一个优点。
![image-20230217140734278](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230217140734278.png)
`Box<dyn Summary>` 表示一个指向实现了 `Summary` trait 的类型的堆上分配对象的指针,由 `Box` 类型进行管理。其中,`dyn` 是 Rust 的关键字,表示动态分发(dynamic dispatch),也就是运行时才决定调用哪个实现。
在 Rust 中,由于不同的类型大小可能不同,因此不能在栈上直接存储一个具体类型的值。通过使用 `Box` 类型可以在堆上分配空间来存储一个值,并将这个值的所有权交给 `Box`,由 `Box` 负责管理这块内存,以确保在所有权范围之外时释放这块内存。
在这个例子中,`Box<dyn Summary>` 表示一个实现了 `Summary` trait 的类型的堆上分配对象的指针,可以指向实现 `Summary` trait 的任意类型的对象,由 `Box` 类型进行所有权管理。由于 `dyn` 表示动态分发,所以当你调用对象的方法时,Rust 在运行时才决定具体调用哪个实现。
**关于Box<T>、Box<dyn Trait>*** :
1: `Box<T>` 类型可以通过解引用操作符 `*` 来获取其底层的值 `T`,但是这只适用于那些在编译时已知**大小的类型**,例如整数、浮点数、指针等。因为在 Rust 中,解引用操作符会尝试将一个指针类型解引用为其指向的值类型,然后将其复制到一个栈上的变量中,而这个栈上的变量必须具有已知的大小。而在 Rust 中,`String` 类型和 `&str` 类型的大小都是在运行时动态分配的,因此不能直接使用解引用操作符来获取它们的值。
2: 在使用 `Box<dyn Trait>` 时,通常情况下**不需要使用解引用操作符 `*`**,因为 `Box<T>` 类型会**自动解引用**为 `T` 类型。这意味着你可以直接调用 `Box<dyn Trait>` 实例上的方法,就好像它是实现了 `Trait` 特征的结构体一样。因此,如果你想要调用 `Box<dyn Trait>` 实例上的方法,可以直接使用点号操作符来调用,而不需要使用 `*` 来解引用。( 也可以手动解引用,(*).function() )
关于从`Box<dyn Trait>` 类型的值转换为具体的实现类型
`downcast` 方法来尝试将 `Box<dyn Trait>` 类型的值转换为具体的实现类型,如果成功,则返回一个 `Box<T>` 类型的值,其中 `T` 是实现了 `Trait` 特征的类型。如果转换失败,则返回一个 `Err` 类型的值。
![image-20230220112440501](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230220112440501.png)
函数返回特征,通过特征(接口 多态?)可以使用对应的方法。若想将特征转为struct可以使用`downcast` 。但`downcast` 方法只能用于将 `Any` 类型的对象转换为其实际类型,不能用于将 trait object 转换为其实际类型。这是因为 trait object 中不包含实际类型信息,所以无法进行动态类型转换。
常量类型使用`downcast`:
![image-20230220131848528](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230220131848528.png)
自定义类型使用downcast失败。总有各种各样的错误。采取了直接实现any以及实现any的子类,皆失败。
## 使用Trait Bound有条件的实现方法
为实现**指定特征**的结构,提供特定方法。
````rust
use std::fmt::Display;
struct Pair<T> {
x: T,
y: T,
}
impl<T> Pair<T> {
fn new(x: T, y: T) -> Self {
Self { x, y }
}
}
impl<T: Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.x >= self.y {
println!("The largest member is x = {}", self.x);
} else {
println!("The largest member is y = {}", self.y);
}
}
}
````
第一个impl块,返回一个新实例的函数`Pair<T>`。 但是在下一个`impl`块中,仅当其内部类型实现了 Display 和 PartialOrd 特征时,`Pair<T>`才可使用此方法。
在动态类型语言中,如果我们在没有定义方法的类型上调用方法,我们会在**运行时**遇到错误。但是 Rust 将这些错误移至**编译时**。

@ -0,0 +1,260 @@
# 使用生命周期验证引用
生命周期是我们已经使用过的另一种通用类型。生命周期不是确保一个类型具有我们想要的行为,而是确保引用在我们需要时有效。**每个引用都有生命周期,这是该引用的有效范围。**
大多数时候,生命周期是隐式的和推断的,就像大多数时候,类型是推断的一样。只有在可能存在多种类型时,我们才必须注释类型。以类似的方式,当引用的生命周期可以通过几种不同的方式相关时,我们必须注释生命周期。Rust 要求我们使用通用**生命周期**参数来注释关系,以确保在运行时使用的实际引用**绝对有效**。
## 使用生命周期防止悬挂引用
```rust
fn main() {
let r;
{
let x = 5;
r = &x;
}
println!("r: {}", r);
}
```
x在内部范围外失效,在尝试使用被引用的x时,已超出了范围。故而无法编译。
## 借用检查器
Rust 编译器有一个*借用检查器*,它比较范围以确定所有借用是否有效。
```rust
fn main() {
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {}", r); // |
} // ---------+
```
这里,我们用'a注释了r的生命周期,用'b注释了x的生命周期。在编译时,Rust比较两个生存期的大小,并看到r的生存期为'a,但它引用的内存的生存期为'b。程序被拒绝,因为'b比'a短:**引用对象的生存期没有引用的生存期长。**
```rust
fn main() {
let x = 5; // ----------+-- 'b
// |
let r = &x; // --+-- 'a |
// | |
println!("r: {}", r); // | |
// --+ |
} // ----------+
```
这里,x的生命周期为'b,在这种情况下大于'a。这意味着r可以引用x,因为Rust知道r中的引用总是有效的,而x是有效的。
## 函数中的通用生命周期
只有当一个函数的参数同时满足以下两个条件时,才需要使用生命周期注解:
1. 参数是一个**引用类型**,即 &T 或 &mut T。
2. 参数在函数体内**被使用**,且函数**返回一个引用类型**,即 &T 或 &mut T。
在这种情况下,编译器需要知道引用的生命周期,以便确保函数返回的引用不会超出参数引用的生命周期。如果参数是在堆上分配的,而不是引用类型,则无需使用生命周期注解。例如,Box<T> 类型的参数就不需要使用生命周期注解。
函数将接受两个字符串切片,并返回一个(较长)字符串切片。
![image-20230221085552365](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230221085552365.png)
错误原因:因为Rust无法判断所返回的引用是指向x还是y。且当参数传递值有误时,if-else不会执行,故而不能确定返回的引用是否总有效。
## 生命周期注释语法
生命周期不会改变任何引用的生命期。它们描述了多个引用彼此之前生存期的关系。函数可以接受任何生命期的引用,通过指定泛型生命期参数。
生命周期注释的语法有点不同寻常:生命周期参数的名称必须以**撇号(')开头**,通常都是**小写**的,而且非常短,就像泛型类型一样。大多数人使用名称'a '作为第一个生命周期注释。我们将生命周期参数注释放在引用的&之后,使用一个**空格**将注释与引用的类型分开。
```rust
&i32 // a reference
&'a i32 // a reference with an explicit lifetime
&'a mut i32 // a mutable reference with an explicit lifetime
```
一个生命周期注释本身没有什么意义,这些注释是为了告诉Rust多个引用的通用生命周期参数如何相互关联。
## 函数签名中的生命周期注释
要在函数签名中使用生命期注释,需要在函数名和参数列表之间的尖括号内声明**泛型生命期参数**。
该签名表达以下约束:**只要两个参数都有效,返回的引用就有效。**这是**参数的生存期**和**返回值**之间的关系。我们将生命周期命名为'a,然后将它添加到每个引用中,
![image-20230221094141799](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230221094141799.png)
函数签名现在告诉Rust,对于某个生命期'a,函数接受两个形参,这两个形参都是至少与生命期'a一样长的字符串切片。函数签名还告诉Rust,从函数返回的字符串切片将至少与生命期'a一样长。在实践中,这意味着最长函数返回的引用的生命期与函数参数引用的值的生命期中较小的那个相同。这些关系就是我们在分析代码时希望Rust使用的关系。
在函数中注释生命周期时,注释放在函数签名中,而不是函数体中。生命周期注释成为函数契约的一部分,就像签名中的类型一样。让函数签名包含生命周期契约意味着Rust编译器所做的分析可以更简单。
当我们将具体引用传递给longest时,被替换为'a的具体生命期是x的作用域中与y的作用域**重叠的部分**。换句话说,泛型生命期'a将获得等于x和y的生命期中**较小的那部分**的具体生命期。
一种情况:
![image-20230221111605523](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230221111605523.png)
在本例中,string1在外部作用域结束之前都有效,string2在内部作用域结束之前都有效,result引用的值在内部作用域结束之前都有效。故可以运行。
另一种情况:此代码会返回string1的引用,同时string1还未超出作用域,按理来说可以打印。但是rust只认生存期中较小的。故而报错。
![image-20230221111749378](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230221111749378.png)
## Thinking in Terms of Lifetimes
指定生命周期参数的方式取决于函数正在执行的操作。eg:若只返回第一个形参,而不是最长的,就不需要在y上指定生存期。
![image-20230221115126654](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230221115126654.png)
当指定返回类型生命周期,但参数与该生命周期无关时:
![image-20230224143110917](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230224143110917.png)
修改方法:
![image-20230224173132537](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230224173132537.png)
## Lifetime Annotations in Struct Definitions
以下情况需要在结构定义中的每个**引用**上添加一个生命周期注释。
![image-20230228095737271](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230228095737271.png)
生命周期注解意味着 ImportantExcerpt 的实例不能超出它在 part和part2 字段中所持有的引用的生命周期。
![image-20230228101312917](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230228101312917.png)
使用String时:
![image-20230228095514558](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230228095514558.png)
## Lifetime Elision
每个引用都有生命周期。需为引用的函数或结构指定生命周期参数。但是也有一些函数会有特殊情况。下面函数编译时没有生命周期。(参数和返回类型是引用)
```rust
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
```
在Rust早期(pre-1.0)此代码不会被编译,因为每个引用都需要一个明确的生命周期。故而签名应写成这样:
```rust
fn first_word<'a>(s: &'a str) -> &'a str {
```
但是随着发展,rust引用分析中添加了 *生命周期省略规则*。 这是编译器会考虑的一组特定情况,如果代码符合此情况,则无需显式编写生命周期。
函数或方法参数的生命周期称为*输入生命周期,*返回值的生命周期称为*输出生命周期。*
当**没有显式注释时**,编译器**使用三个规则**来确定引用的生命周期。第一个规则适用于输入生命周期,第二条和第三条规则适用于输出生命周期。如果编译器到达三个规则的末尾仍然有它无法确定生命周期的引用,编译器将停止并报错。规则适用于fn定义和impl块。
规则1:编译器为每个引用参数分配一个生命周期参数。具体来讲,具有一个参数的函数获得一个生命周期参数: fn foo<'a>(x: &'a i32); 有两个参数的函数有两个独立的生命周期参数:fn foo<'a, 'b>(x: &'a i32, y: &'b i32)。以此类推。
规则2:如果只有一个输入生命周期参数,则该生命周期将分配给所有输出生命周期参数:fn foo<'a>(x: &'a i32) -> &'a i32。
规则3(仅使用方法签名):如果有多个输入生命周期参数,但其中一个是 &self 或 &mut self,因为这是一个方法,self 的生命周期将分配给所有输出生命周期参数。这第三条规则使得方法更易于阅读和编写,因为需要的符号更少。
举个例子:
```rust
// 我们定义的
fn first_word(s: &str) -> &str {
// 第一条规则适用:经过第一条规则后
fn first_word<'a>(s: &'a str) -> &str {
// 第二条规则适用:经过第二条规则后
fn first_word<'a>(s: &'a str) -> &'a str {
// 满足编译器要求,每个引用都有生命周期
// 另外定义
fn longest(x: &str, y: &str) -> &str {
// 第一条规则适用:经过第一条规则后
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {
// 第二条规则不适应,第三条也不适用
// 返回值无生命周期,无法编译。
```
## Lifetime Annotations in Method Definitions
![image-20230228162418374](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230228162418374.png)
根据生命周期省略规则的第一条规则,Rust 会为 `&self``announcement` 分别分配各自的生命周期。然后,由于其中一个参数是 `&self`,所以返回类型的生命周期就等同于 `&self` 的生命周期。
```rust
impl<'a> ImportantExcerpt<'a> {
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Attention please: {}", announcement);
self.part
}
}
```
## The Static Lifetime
特殊的生命周期`static`,表示受影响的引用可以在程序的整个生命周期内存在。所有的字符串字面值都有 `static` 生命周期。因此它始终可用。因此,所有字符串字面值的生命周期都是 `static`
![image-20230228163727084](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230228163727084.png)
**不要使用static**来**解决悬挂引用**或**生命周期**不匹配问题。应修复问题,而不是直接把生命周期设为整个程序/
## Generic Type Parameters, Trait Bounds, and Lifetimes Together
示例代码:
![image-20230228165245740](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230228165245740.png)

@ -0,0 +1,129 @@
# 泛型类型、特征和生存期
泛型,处理重复的工具。
## 通过提取函数来消除重复
泛型允许我们用表示多种类型的占位符替换特定类型,以消除代码重复。
![image-20230214100415253](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230214100415253.png)
## 泛型
我们使用泛型来为函数签名或结构等项创建定义,然后可以将其用于许多不同的具体数据类型。
### 在函数定义中
在定义使用泛型的函数时,我们将泛型放在函数的签名中,通常在这里指定参数和返回值的数据类型。
不使用泛型时:
![image-20230214101609436](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230214101609436.png)
泛型函数定义:
```rust
fn largest<T>(list: &[T]) -> &T {}
```
函数最大是对某种类型T的泛型。该函数有一个名为list的形参,它是类型T的值的切片。最大函数将返回对相同类型T的值的引用。
![image-20230214103935848](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230214103935848.png)
`std::cmp::PartialOrd` 是 Rust 标准库中的一个 trait,它提供了一种比较类型实例的方法。该 trait 定义了关系运算符(例如小于号(`<`))的行为。
实现 `std::cmp::PartialOrd` trait 的类型可以使用这些关系运算符进行比较。这些比较可以用于实现排序算法,例如,您可以使用比较运算符对一组数据进行排序。[PartialOrd 文档。](https://doc.rust-lang.org/stable/std/cmp/trait.PartialOrd.html)
### 在结构定义中
可以使用<>语法定义结构,在一个或多个字段中使用泛型类型参数。
![image-20230214111131732](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230214111131732.png)
x和y都是泛型,但可以有不同的类型,可以使用多个泛型类型参数、
![image-20230214111655994](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230214111655994.png)
### 在枚举定义中
同处理结构体一样,我们可以定义枚举来保存泛型数据类型的变体。例如Option<T>枚举。
```rust
enum Option<T> {
Some(T),
None,
}
```
Result枚举是两种类型T和E的泛型,并且有两个变体,
### 在方法定义中
![image-20230214115347188](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230214115347188.png)
在Point<T>上定义了一个名为x的方法,该方法返回对字段x中的数据的引用。
![](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230214143218912.png)
该方法测量我们的点到坐标(0.0,0.0)处的点的距离,并使用仅适用于**浮点类型**的数学操作。类型不符时产生的问题:
![image-20230214150121126](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230214150121126.png)
结构定义中的泛型类型参数并不总是与在同一结构的方法签名中使用的参数相同。具体如下:
![image-20230214153201354](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230214153201354.png)
### 使用泛型的代码性能
使用泛型类型不会使程序比使用具体类型运行得更慢。Rust通过在编译时使用泛型执行代码的**单一化**来实现这一点。单一化是通过填充**编译时使用的具体类型**,将泛型代码转换为特定代码的过程。
```rust
#![allow(unused)]
fn main() {
let integer = Some(5);
let float = Some(5.0);
}
```
当Rust编译这段代码时,它执行单化。在这个过程中,编译器读取Option<T>实例中使用的**值**,并识别出两种Option<T>:一种是i32,另一种是f64。因此,它将Option<T>的泛型定义扩展为两个专门用于i32和f64的定义,从而将泛型定义替换为特定的定义。
```rust
enum Option_i32 {
Some(i32),
None,
}
enum Option_f64 {
Some(f64),
None,
}
fn main() {
let integer = Option_i32::Some(5);
let float = Option_f64::Some(5.0);
}
```
泛型Option<T>被替换为编译器创建的特定定义。因为Rust将泛型代码编译为每个实例中指定类型的代码,所以使用泛型没有运行时成本。当代码运行时,它的执行就像手工复制每个定义一样。单一化的过程使得Rust的泛型在运行时非常高效。

@ -0,0 +1,153 @@
![image-20230309152409191](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309152409191.png)
![image-20230309151658572](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309151658572.png)
![image-20230309151916594](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309151916594.png)
### Testing Equality with the assert_eq! and assert_ne! Macros
assert!宏用于执行 == 操作。再此基础上,标准库还提供了一对宏 assert_eq!和assert_ne! 分别比较相等或不相等的两个论点。如果断言失败,它们还会**打印两个值**,这使得更容易看到测试失败的原因;相反,**assert!宏**仅指示它为==表达式获取了一个假值,而**不打印**导致该假值的值。
使用assert_eq!宏,断言失败时:
![image-20230309154720180](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309154720180.png)
使用assert_nq!宏,断言失败时:
![image-20230309155053448](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309155053448.png)
**assert_eq!和assert_ne!**宏分别使用运算符**==和!=**。当断言失败时,这些宏使用调试格式打印它们的参数,这意味着要比较的值必须实现**PartialEq**和**debug**特征。所有基本类型和大多数标准库类型都实现了这些特征。对于自己定义的结构体和枚举,需要实现PartialEq来断言这些类型的相等性。当断言失败时,还需要实现Debug来打印值。**\#[derive(PartialEq, Debug)]。**
### Adding Custom Failure Messages
可以将要与**失败消息**一起打印的自定义消息添加为`assert!`、`assert_eq!`和`assert_ne!`宏的可选参数。
![image-20230309161947925](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309161947925.png)
`assert_eq!`和`assert_ne!`时写法:
![image-20230309162455307](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309162455307.png)
### Checking for Panics with should_panic
检查代码是否如所期望的那样处理错误。使用**should_panic**属性,如果函数内的代码**出现panic**,则测试**通过**;如果函数内的代码**不恐慌**,则测试**失败**。
当没有panic时,效果如下:
![image-20230309165534578](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309165534578.png)
should_panic 即使测试恐慌的原因与我们预期的不同,should_panic测试也会通过。可通过添加一个可选的预期参数,让should_panic测试更准确。
![image-20230310134325891](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310134325891.png)
失败情况:
![image-20230310134601301](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310134601301.png)
### Using Result<T, E> in Tests
测试失败时会产生恐慌,可以通过 Result<T,E> 返回一个Err, 使恐慌消失。
![image-20230310142453605](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310142453605.png)
## 控制测试的运行方式
默认情况下,由 cargo test 生成的二进制文件会**并行**运行所有测试,并**捕获**测试运行期间生成的**输出**,以便更容易阅读与测试结果相关的输出信息。但是,您可以使用命令行选项来更改此默认行为。
### Running Tests in Parallel or Consecutively
当你运行多个测试时,默认情况下它们会使用**线程并行运行**,这意味着它们完成测试的速度更快,你可以更快地得到反馈。因为测试同时运行,所以你必须确保你的测试不依赖于彼此或任何共享状态,包括共享环境,例如当前工作目录或环境变量。
默认情况下,`cargo test` 会根据可用的 CPU 核心数量自动选择并发运行测试的线程数。具体来说,它会为每个 CPU 核心分配一个线程,最多不会超过8个线程。
```rust
$ cargo test -- --test-threads=1
```
测试线程数设为1,即不使用并行性。测试时间会更多,但如果用例间共享状态,则测试不会相互干扰。
### Showing Function Output
默认情况下,如果**测试通过**,Rust 的测试库会捕获任何打印到标准输出的内容。
可使用 $ cargo test -- --show-output
![image-20230310145558520](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310145558520.png)
使用 $ cargo test -- --nocapture时:
![image-20230310145404690](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310145404690.png)
相对而言 show-output 界面更好。
`--show-output` 选项用于**显示**测试运行期间的输出信息。`--nocapture` 选项用于**禁止捕获**测试运行期间的输出信息。如果同时使用这两个选项,`--show-output` 选项会覆盖 `--nocapture` 选项。
### Running a Subset of Tests by Name
可以指定要运行的部分代码。会按名称进行匹配,匹配多少运行多少。cargo test [名称],只要测试用例名称包含提供的字段,就会运行(多个或单个)。
当名称设为add时:
![image-20230310151923207](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310151923207.png)
### Filtering to Run Multiple Tests
\#[ignore] 属性,使测试时排除他们。
![image-20230310152114535](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310152114535.png)
`cargo test -- --ignored``cargo test -- --include-ignored` 都是用来运行被标记为`ignored`的测试的。
`cargo test -- --ignored` 运行被标记为`ignored`的测试,但是不会运行没有被标记为`ignored`的测试。这个选项是用来运行已知失败的测试或者还没有完成的测试的。也就是说,它只运行被标记为`ignored`的测试,而不管其他的测试是否通过。
例如,如果你有一个测试被标记为`ignored`,并且在这个测试通过之前你不想运行其他测试,你可以使用`cargo test -- --ignored`来只运行这个测试。
![image-20230310152809699](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310152809699.png)
相反,`cargo test -- --include-ignored` 会运行所有测试,包括那些被标记为`ignored`的测试。这个选项是用来在某些情况下,你需要知道所有测试的状态,包括已知失败的测试或还没有完成的测试的时候使用的。
![image-20230310152735856](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310152735856.png)

@ -0,0 +1,125 @@
## Test Organization
Rust社区认为测试主要分为两类:单元测试和集成测试。单元测试较小且更集中,每次单独测试**一个模块**,并且可以测试**私有接口**。集成测试则完全在库外部,并且与任何其他外部代码相同的方式使用代码,只使用**公共接口**,并且可能在每个测试中使用**多个模块**。
### 单元测试
单元测试的目的是在与其他代码**隔离**的情况下测试每个代码单元。传统做法是在每个文件中创建名为**tests的模块**来包含测试函数,并用`cfg(test)`注释该模块。
在测试模块上的 `#[cfg(test)]` 注解告诉 Rust 仅在运行 `cargo test` 时编译和运行测试代码,而不在运行 `cargo build` 时编译。而因为集成测试在不同的目录中,它们不需要 `#[cfg(test)]` 注解。 cfg属性代表configuration,它告诉Rust只有在给定某个配置选项时才应该包含下面的项,在本例中,配置选项是test。
`cfg` 属性可以使用多个参数指定编译条件,其中一些常见的参数如下:
- `test`:仅在运行测试时编译
- `debug_assertions`:仅在使用 `cargo build``cargo run` 命令的 `--debug` 模式下编译
- `unix``windows`:仅在目标操作系统为 Unix 或 Windows 时编译
- `target_arch``target_os`:仅在目标 CPU 架构和操作系统匹配时编译
#### 测试私有函数
![image-20230310160912780](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310160912780.png)
### 集成测试
集成测试只能调用属于您库的公共 API 的函数。目的是测试您的库的许多部分是否可以正确协同工作。不需要添加**\#[cfg(test)]**,Cargo对tests目录进行了特殊处理,仅在运行Cargo测试时才编译该目录中的文件。
#### 测试目录
测试目录在项目目录顶层,紧挨着src。目录名称必须为tests。
```
adder
├── Cargo.lock
├── Cargo.toml
├── src
   └── lib.rs
└── tests
└── integration_test.rs
```
如果单元测试失败,则不会有任何集成和文档测试的输出,因为这些测试只有在所有单元测试都通过时才会运行。按顺序来的,如果集成测试出错,那么不会有文档测试输出。
运行测试无误时结果如下:
![image-20230310163621820](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310163621820.png)
集成测试也可以指定名称来运行:
![image-20230310165323146](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310165323146.png)
运行特定集成测试文件中的所有测试,`cargo test --test 完整文件名`
![image-20230310164959908](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310164959908.png)
#### 集成测试中子模块
集成测试可以根据它们测试的功能将测试函数分组。同时当存在多个集成测试文件时,可以提取出相同的行为放入公共模块。
##### 集成测试中添加通用模块:
错误位置:
![image-20230310171134280](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310171134280.png)
正确位置:
![image-20230310171717119](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310171717119.png)
运行图(不含show-output):
![image-20230310171944280](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310171944280.png)
使用旧的 Rust 命名约定,即文件名为 `mod.rs` 的文件被视为包含该目录的模块的**主要文件**,而不是一个单独的集成测试文件。因此,Rust **不会将其视为一个集成测试文件**。这种约定在较旧的 Rust 代码中很常见,因此 Rust 保留了对这种方式的支持,以保持向后兼容性。
项目目录:
```
├── Cargo.lock
├── Cargo.toml
├── src
   └── lib.rs
└── tests
├── common
   └── mod.rs
└── integration_test.rs
```
### 针对二进制包的集成测试
如果项目中只包含一个 `src/main.rs` 文件而没有 `src/lib.rs` 文件,我们无法在 `tests` 目录中创建集成测试,并使用 `use` 语句将 `src/main.rs` 中定义的函数引入作用域。这是因为**只有库箱(library crate)才会公开其他箱可以使用的函数,而二进制箱本身可以单独运行**。因此,Rust 项目通常将**重要逻辑**放在 `src/lib.rs` 文件中,并在 `src/main.rs` 文件中**调用**这些逻辑,以此为基础来构建二进制箱。
**二进制箱可以进行单元测试**。在二进制箱中,可以将测试放在 `src/main.rs` 文件的底部,就像在库中将测试放在与源码文件相同的文件中一样。您可以使用 `#[cfg(test)]` 属性来标记测试函数,以便它们只在运行 `cargo test` 命令时进行编译和运行。请注意,因为二进制箱的目的是作为独立的可执行文件运行,而不是作为库被其他crate使用,因此在二进制箱中编写的测试通常会更注重对**集成测试的覆盖**,而不是对库代码进行单元测试。
单元测试:
![image-20230312135712101](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230312135712101.png)
集成测试(失败):
![image-20230312140138211](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230312140138211.png)
集成测试加上lib(成功):
![image-20230312140255410](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230312140255410.png)

@ -0,0 +1,354 @@
# Automated Tests
## How to Write Tests
测试函数的主体通常执行以下三个操作:
1.设置任何需要的数据或状态。
2.运行要测试的代码。
3.断言的结果是你所期望的。
Rust专门为编写执行这些操作的测试提供的特性,其中包括test属性、一些宏和should_panic属性。
### 测试函数解剖
最简单情况下,rust中的测试是一个用test属性注释的函数。属性是关于 Rust 代码片段的元数据(derive 也是一个属性);再要测试的函数(fn)前,添加#[test]。使用**cargo test**,Rust会构建一个测试运行程序二进制文件,运行带注释的函数,并报告每个测试函数是通过还是失败。
每当我们用 Cargo 创建一个新的库项目时,都会自动为我们生成一个带有测试功能的测试模块。
使用cargo test会产生如下结果:
![image-20230302225047715](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230302225047715.png)
多个测试用例时(有对有错,可以是抛出的panic)结果如下:
![image-20230302230225599](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230302230225599.png)
### Checking Results with the assert! Macro
assert! 宏,是标准库中提供的,主要用于判断条件和预期结果是否相同。相同(true)时无事发生,否则抛出一个panic。
```rust
use super::*;
// tests模块是一个内部模块,所以我们需要将外部模块中测试的代码引入内部模块的范围。
// 我们在这里使用了一个glob,所以我们在外层模块中定义的任何东西都可以用于这个测试模块。
```
对rectangle结构使用assert(正确情况),如下所示:
![image-20230302235254167](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230302235254167.png)
错误情况:
![image-20230303000942014](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230303000942014.png)
![image-20230309152409191](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309152409191.png)
![image-20230309151658572](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309151658572.png)
![image-20230309151916594](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309151916594.png)
![image-20230309152409191](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309152409191.png)
![image-20230309151658572](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309151658572.png)
![image-20230309151916594](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309151916594.png)
### Testing Equality with the assert_eq! and assert_ne! Macros
assert!宏用于执行 == 操作。再此基础上,标准库还提供了一对宏 assert_eq!和assert_ne! 分别比较相等或不相等的两个论点。如果断言失败,它们还会**打印两个值**,这使得更容易看到测试失败的原因;相反,**assert!宏**仅指示它为==表达式获取了一个假值,而**不打印**导致该假值的值。
使用assert_eq!宏,断言失败时:
![image-20230309154720180](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309154720180.png)
使用assert_nq!宏,断言失败时:
![image-20230309155053448](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309155053448.png)
**assert_eq!和assert_ne!**宏分别使用运算符**==和!=**。当断言失败时,这些宏使用调试格式打印它们的参数,这意味着要比较的值必须实现**PartialEq**和**debug**特征。所有基本类型和大多数标准库类型都实现了这些特征。对于自己定义的结构体和枚举,需要实现PartialEq来断言这些类型的相等性。当断言失败时,还需要实现Debug来打印值。**\#[derive(PartialEq, Debug)]。**
### Adding Custom Failure Messages
可以将要与**失败消息**一起打印的自定义消息添加为`assert!`、`assert_eq!`和`assert_ne!`宏的可选参数。
![image-20230309161947925](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309161947925.png)
`assert_eq!`和`assert_ne!`时写法:
![image-20230309162455307](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309162455307.png)
### Checking for Panics with should_panic
检查代码是否如所期望的那样处理错误。使用**should_panic**属性,如果函数内的代码**出现panic**,则测试**通过**;如果函数内的代码**不恐慌**,则测试**失败**。
当没有panic时,效果如下:
![image-20230309165534578](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230309165534578.png)
should_panic 即使测试恐慌的原因与我们预期的不同,should_panic测试也会通过。可通过添加一个可选的预期参数,让should_panic测试更准确。
![image-20230310134325891](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310134325891.png)
失败情况:
![image-20230310134601301](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310134601301.png)
### Using Result<T, E> in Tests
测试失败时会产生恐慌,可以通过 Result<T,E> 返回一个Err, 使恐慌消失。
![image-20230310142453605](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310142453605.png)
## 控制测试的运行方式
默认情况下,由 cargo test 生成的二进制文件会**并行**运行所有测试,并**捕获**测试运行期间生成的**输出**,以便更容易阅读与测试结果相关的输出信息。但是,您可以使用命令行选项来更改此默认行为。
### Running Tests in Parallel or Consecutively
当你运行多个测试时,默认情况下它们会使用**线程并行运行**,这意味着它们完成测试的速度更快,你可以更快地得到反馈。因为测试同时运行,所以你必须确保你的测试不依赖于彼此或任何共享状态,包括共享环境,例如当前工作目录或环境变量。
默认情况下,`cargo test` 会根据可用的 CPU 核心数量自动选择并发运行测试的线程数。具体来说,它会为每个 CPU 核心分配一个线程,最多不会超过8个线程。
```rust
$ cargo test -- --test-threads=1
```
测试线程数设为1,即不使用并行性。测试时间会更多,但如果用例间共享状态,则测试不会相互干扰。
### Showing Function Output
默认情况下,如果**测试通过**,Rust 的测试库会捕获任何打印到标准输出的内容。
可使用 $ cargo test -- --show-output
![image-20230310145558520](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310145558520.png)
使用 $ cargo test -- --nocapture时:
![image-20230310145404690](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310145404690.png)
相对而言 show-output 界面更好。
`--show-output` 选项用于**显示**测试运行期间的输出信息。`--nocapture` 选项用于**禁止捕获**测试运行期间的输出信息。如果同时使用这两个选项,`--show-output` 选项会覆盖 `--nocapture` 选项。
### Running a Subset of Tests by Name
可以指定要运行的部分代码。会按名称进行匹配,匹配多少运行多少。cargo test [名称],只要测试用例名称包含提供的字段,就会运行(多个或单个)。
当名称设为add时:
![image-20230310151923207](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310151923207.png)
### Filtering to Run Multiple Tests
\#[ignore] 属性,使测试时排除他们。
![image-20230310152114535](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310152114535.png)
`cargo test -- --ignored``cargo test -- --include-ignored` 都是用来运行被标记为`ignored`的测试的。
`cargo test -- --ignored` 运行被标记为`ignored`的测试,但是不会运行没有被标记为`ignored`的测试。这个选项是用来运行已知失败的测试或者还没有完成的测试的。也就是说,它只运行被标记为`ignored`的测试,而不管其他的测试是否通过。
例如,如果你有一个测试被标记为`ignored`,并且在这个测试通过之前你不想运行其他测试,你可以使用`cargo test -- --ignored`来只运行这个测试。
![image-20230310152809699](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310152809699.png)
相反,`cargo test -- --include-ignored` 会运行所有测试,包括那些被标记为`ignored`的测试。这个选项是用来在某些情况下,你需要知道所有测试的状态,包括已知失败的测试或还没有完成的测试的时候使用的。
![image-20230310152735856](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310152735856.png)
## Test Organization
Rust社区认为测试主要分为两类:单元测试和集成测试。单元测试较小且更集中,每次单独测试**一个模块**,并且可以测试**私有接口**。集成测试则完全在库外部,并且与任何其他外部代码相同的方式使用代码,只使用**公共接口**,并且可能在每个测试中使用**多个模块**。
### 单元测试
单元测试的目的是在与其他代码**隔离**的情况下测试每个代码单元。传统做法是在每个文件中创建名为**tests的模块**来包含测试函数,并用`cfg(test)`注释该模块。
在测试模块上的 `#[cfg(test)]` 注解告诉 Rust 仅在运行 `cargo test` 时编译和运行测试代码,而不在运行 `cargo build` 时编译。而因为集成测试在不同的目录中,它们不需要 `#[cfg(test)]` 注解。 cfg属性代表configuration,它告诉Rust只有在给定某个配置选项时才应该包含下面的项,在本例中,配置选项是test。
`cfg` 属性可以使用多个参数指定编译条件,其中一些常见的参数如下:
- `test`:仅在运行测试时编译
- `debug_assertions`:仅在使用 `cargo build``cargo run` 命令的 `--debug` 模式下编译
- `unix``windows`:仅在目标操作系统为 Unix 或 Windows 时编译
- `target_arch``target_os`:仅在目标 CPU 架构和操作系统匹配时编译
#### 测试私有函数
![image-20230310160912780](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310160912780.png)
### 集成测试
集成测试只能调用属于您库的公共 API 的函数。目的是测试您的库的许多部分是否可以正确协同工作。不需要添加**\#[cfg(test)]**,Cargo对tests目录进行了特殊处理,仅在运行Cargo测试时才编译该目录中的文件。
#### 测试目录
测试目录在项目目录顶层,紧挨着src。目录名称必须为tests。
```
adder
├── Cargo.lock
├── Cargo.toml
├── src
   └── lib.rs
└── tests
└── integration_test.rs
```
如果单元测试失败,则不会有任何集成和文档测试的输出,因为这些测试只有在所有单元测试都通过时才会运行。按顺序来的,如果集成测试出错,那么不会有文档测试输出。
运行测试无误时结果如下:
![image-20230310163621820](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310163621820.png)
集成测试也可以指定名称来运行:
![image-20230310165323146](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310165323146.png)
运行特定集成测试文件中的所有测试,`cargo test --test 完整文件名`
![image-20230310164959908](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310164959908.png)
#### 集成测试中子模块
集成测试可以根据它们测试的功能将测试函数分组。同时当存在多个集成测试文件时,可以提取出相同的行为放入公共模块。
##### 集成测试中添加通用模块:
错误位置:
![image-20230310171134280](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310171134280.png)
正确位置:
![image-20230310171717119](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310171717119.png)
运行图(不含show-output):
![image-20230310171944280](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310171944280.png)
使用旧的 Rust 命名约定,即文件名为 `mod.rs` 的文件被视为包含该目录的模块的**主要文件**,而不是一个单独的集成测试文件。因此,Rust **不会将其视为一个集成测试文件**。这种约定在较旧的 Rust 代码中很常见,因此 Rust 保留了对这种方式的支持,以保持向后兼容性。
项目目录:
```
├── Cargo.lock
├── Cargo.toml
├── src
   └── lib.rs
└── tests
├── common
   └── mod.rs
└── integration_test.rs
```
### 针对二进制包的集成测试
如果项目中只包含一个 `src/main.rs` 文件而没有 `src/lib.rs` 文件,我们无法在 `tests` 目录中创建集成测试,并使用 `use` 语句将 `src/main.rs` 中定义的函数引入作用域。这是因为**只有库箱(library crate)才会公开其他箱可以使用的函数,而二进制箱本身可以单独运行**。因此,Rust 项目通常将**重要逻辑**放在 `src/lib.rs` 文件中,并在 `src/main.rs` 文件中**调用**这些逻辑,以此为基础来构建二进制箱。
**二进制箱可以进行单元测试**。在二进制箱中,可以将测试放在 `src/main.rs` 文件的底部,就像在库中将测试放在与源码文件相同的文件中一样。您可以使用 `#[cfg(test)]` 属性来标记测试函数,以便它们只在运行 `cargo test` 命令时进行编译和运行。请注意,因为二进制箱的目的是作为独立的可执行文件运行,而不是作为库被其他crate使用,因此在二进制箱中编写的测试通常会更注重对**集成测试的覆盖**,而不是对库代码进行单元测试。
单元测试:
![image-20230312135712101](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230312135712101.png)
集成测试(失败):
![image-20230312140138211](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230312140138211.png)
集成测试加上lib(成功):
![image-20230312140255410](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230312140255410.png)

@ -0,0 +1,348 @@
# An I/O Project: Building a Command Line Program
## Accepting Command Line Arguments
创建一个项目(minigrep),接受两个命令行参数:文件路径和要搜索的字符串。
![image-20230313232526527](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230313232526527.png)
### 读取参数值
需要`std::env::args`来读取命令行参数的值。此函数返回一个命令行参数的迭代器给`minigrep`。
迭代器:1.迭代器生成一系列值。2.我们可以调用迭代器上的collect方法将其转换为包含迭代器生成的所有元素的集合,例如vector。
![image-20230313235623588](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230313235623588.png)
在需要的函数嵌套在多个模块中的情况下,选择将父模块而不是函数引入作用域。这样可以使用std::env中的其他函数。这也比添加use std::env::args 然后只使用args调用函数要少一些歧义。
调用 dbg! 宏会将信息输出到标准错误控制台流 (stderr),而 println! 则会输出到标准输出控制台流 (stdout)
**注意 std::env::args 在其参数包含无效 Unicode 字符时会 panic。**如果你需要接受包含无效 Unicode 字符的参数,需使用 **std::env::args_os** 代替。**这个函数返回 OsString 迭代器而不是 String 迭代器。**这里出于简单考虑使用了 std::env::args,因为 OsString 值每个平台都不一样而且比 String 值处理起来更为复杂。
### 保存参数值到变量
![image-20230314001031837](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230314001031837.png)
collect函数来创建多种类型的集合,因此我们显式声明了args的类型,以指定我们想要一个字符串向量。虽然在Rust中我们很少需要显式声明类型,但collect是一个经常需要显式声明的函数,因为Rust不能推断出想要的集合类型。
## 读取文件
![image-20230320105556589](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230320105556589.png)
## 重构以改进模块化和错误处理
1. 首先将main程序中的多个任务解析成单独函数,模块化。
2. 将配置变量存到一个结构中,以明确用途。再此代码中即app_name,query,file_path。防止后期变量变多后,理解困难。
3. expect总是打印相同的错误信息,没有提供有用信息。
4. 最好将所有的错误处理代码都在一个地方,便于后期维护。
### 二进制项目的模块化
将多个任务的责任分配给主要功能的组织问题在许多二进制项目中都很常见。因此,Rust社区已经制定了指南,以在main函数开始变得庞大时分离二进制程序的不同关注点。这个过程包括以下步骤:
1. 将项目拆分成main.rs和lib.rs,将逻辑部分移至lib.rs。
2. 只把简单的命令行解析逻辑留在main.rs文件中。
3. 当命令行解析逻辑开始变得复杂时,将其从main.rs文件中提取出来,并将其移动到lib.rs文件中。
拆分后再main函数中保留的职责应在以下几个方面:
- 使用参数值调用命令行解析逻辑
- 设置任何其他配置
- 调用lib.rs中的run函数
- 处理运行函数时可能产生的错误
这种模式是关注点分离:main.rs文件负责运行程序,而lib.rs文件则处理手头任务的所有逻辑。由于你无法直接测试main函数,因此这种结构让你通过将逻辑移动到lib.rs文件中的函数中来测试程序的所有逻辑。留在main.rs文件中的代码将足够小,以便通过阅读它来验证其正确性。
#### 提取参数函数
将提取命令行解析的逻辑封装到一个函数中,将此逻辑移至src/lib.rs中。(目前函数暂时放在src.main.rs)
![image-20230414091711698](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230414091711698.png)
![image-20230414091758726](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230414091758726.png)
不再通过下标在主函数取变量,将整个向量传递给parse_config函数。parse_config决定哪个参数应该放在哪个变量中,并将值传递回main。在main中创建查询变量和file_path变量,但是main不再负责决定命令行参数和变量如何对应。
#### 分组配置值
目前使用parse_config函数获取一个元组。如果变量名不明确,在main中对应会很困难,其次没有将这两个相关的配置值关联起来。现将返回值修改为结构体。
![image-20230414093953231](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230414093953231.png)
添加结构体Config用来存取query和file_path。将parse_config的返回值设为Config。parse_config函数体中,因为返回值不再是&str,结构体字段为String故需要取到String的所有权。但main中的args变量是参数值的所有者,只允许parse_config函数借用它们,这意味着如果Config试图获得args中的值的所有权,我们就违反了Rust的借用规则。
可通过对值调用clone方法来为Config实例提供数据的完整副本,这比存储对字符串数据的引用需要更多的时间和内存(增加运行成本),好处是克隆数据不需要管理引用的生存期。
#### 为Config创建构造函数
将创建Config的逻辑从parse_config函数移至Config结构体的构造函数中。[Struct_name]::new是更惯用的创建结构体实例的办法。
![image-20230414104148535](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230414104148535.png)
#### 修改错误处理
此程序当运行时传递的配置小于2个时,将会返回错误信息"index of bounds"。而这对用户理解错误没什么实质帮助。
##### 改进错误提示
在new函数中检查切片长度,当会产生下标越界时,panic会结束程序并提示"not enough arguments"错误。
![image-20230414110235170](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230414110235170.png)
现在当输入参数不足时,会返回给用户一个更合理的错误信息。但panic更适合处理编程问题,而非返回错误提示。当预期失败时,返回Result比panic!更合适。
##### 返回result代替panic!
首先将函数名改为build,因为new函数不应失败。build函数在成功情况下返回一个Config实例,在失败时返回一个&'静态str。我们的错误值将始终是具有“静态生命期”的字符串字面量。
![image-20230414113937408](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230414113937408.png)
函数体不再调用panic!当用户没有传递足够的参数时,返回一个Err值,并将Config返回值包装在一个Ok中。
##### 使用config::build处理错误
exit(1)函数,以非零退出状态是一种约定表示程序以错误状态退出。
![image-20230414144032624](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230414144032624.png)
错误情况下运行的闭包中的代码只有两行:打印err值,然后使用process::exit函数停止程序并返回作为退出状态代码传递的数字。这类似于panic!但是不再得到所有额外的输出。
`unwrap_or_else()`方法接受一个回调函数作为参数,如果Result的值是Ok,则返回其内部的值,如果Result的值是Err,则调用传递给unwrap_or_else的回调函数,并返回其返回值。回调函数允许对错误情况进行自定义处理。
#### 从main中提取逻辑
将程序中不涉及配置和处理错误的逻辑,封装到run函数中。
![image-20230414145505271](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230414145505271.png)
##### 修改run的错误处理
![image-20230414152416568](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230414152416568.png)
`Box<dyn Error>`意味着函数将返回一个实现`Error`特征的类型,但我们不必指定返回值是什么特定类型。这使我们能够灵活地返回在不同错误情况下可能具有不同类型的错误值。该`dyn`关键字是“动态”的缩写。
##### 在main函数中处理返回的错误
![image-20230414155458760](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230414155458760.png)
使用if let来检查run返回的Err值,因为run()在成功的情况下,我们只关心检测错误,所以我们不需要unwrap_or_else来返回未包装的值,它只会是()。
![image-20230414160204748](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230414160204748.png)
#### 将代码拆分到Library Crate
将所有不是`main`函数的代码从*src/main.rs*移动到 *src/lib.rs*
- 函数`run`定义
- 相关`use`声明
- `Config`的定义
- `Config::build`函数定义
![image-20230414162340556](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230414162340556.png)
将lib.rs里的struct、fields、method和function都设为pub,通过pub关键字设为公共API。
## 使用测试驱动开发(TDD)的过程来开发库的功能
使用测试驱动开发(TDD)的过程将搜索逻辑添加到minigrep程序中,具体步骤如下:
1. 编写一个失败的测试并运行它,以确保它以你期望的方式失败。
2. 编写或修改足够的代码以使新测试通过。
3. 重构你刚添加或修改的代码,并确保测试继续通过。
4. 重复从步骤1开始!
### 编写一个失败的测试
在src/lib.rs中添加一个tests模块和一个test函数,就像在第11章中所做的那样。test函数指定了我们希望search函数具有的行为:它将获取一个查询和要搜索的文本,并仅返回包含查询的文本行。
![image-20230414170752258](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230414170752258.png)
我们需要在search的签名中定义显式生命周期'a,并将该生命周期与contents参数和返回值一起使用。生命周期参数指定哪个参数生命周期与返回值的生命周期相关联。在这种情况下,我们表明返回的向量应该包含引用参数contents的片段的字符串切片(而不是参数query的切片)。
换句话说,我们告诉Rust,search函数返回的数据将与在contents参数中传递给search函数的数据一样长寿。切片引用的数据需要对引用有效;如果编译器假定我们正在使用query而不是contents的字符串切片,它将无法正确进行安全检查。
#### 编写代码以通过测试
目前,我们的测试失败,因为我们总是返回一个空向量。为了修复它并实现search,我们的程序需要按照以下步骤进行:
1. 遍历contents的每一行。
2. 检查该行是否包含我们的查询字符串。 如果包含,则将其添加到我们正在返回的值列表中。 如果不包含,则什么都不做。
3. 返回匹配的结果列表。
##### 使用lines方法遍历每行
Rust有一个有用的方法来处理字符串逐行迭代,方便地命名为lines。lines方法返回一个迭代器。
```rust
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
for line in contents.lines(){
// do something with line
}
}
```
#####
##### 遍历每行查找query并处理结果
```rust
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let mut results = Vec::new();
for line in contents.lines(){
if line.contains(query){
results.push(line);
}
}
results
}
```
测试结果:
![image-20230417094038803](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230417094038803.png)
##### 在run() 中使用 search()
```rust
pub fn run(config: Config) -> Result<(), Box<dyn Error>>{
let contents = fs::read_to_string(config.file_path)?;
for line in search(&config.query, &contents){
println!("{}", line);
}
// println!("With text: \n{}", contents);
Ok(())
}
```
运行结果:
![image-20230417095454589](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230417095454589.png)
## 使用环境变量
通过环境变量来打开不区分大小写的搜索选项。
### 为不区分大小写搜索函数编写一个失败的测试
继续遵循TDD过程,首先编写一个失败的测试。添加一个search_case_insensitive(),当环境变量有值时将调用该函数。
![image-20230417102247494](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230417102247494.png)
分别在测试里新增内容,检测就内容是否会无视大小写,以及新内容能否通过大小写匹配。
### 实现search_case_insensitive()
search_case_insensitive()与search函数唯一不同的地方,在于函数体内将query和line小写。下图为添加后的测试结果:
![image-20230417104634755](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230417104634755.png)
为结构体添加字段来选择是否切换大小写
```rust
pub struct Config {
pub query: String,
pub file_path: String,
pub ignore_case: bool, // 用于判断是否检测大小写
}
```
在run()中,通过字段选择调用的函数
![image-20230417110935679](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230417110935679.png)
添加环境变量代码,处理环境变量的函数在标准库中的**env模块**(std::env)中,故将模块置于src/lib.rs的顶部。
![image-20230417112250588](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230417112250588.png)
env:var() 接收一个环境变量名称,如果此环境变量未设置将会返回VarError,如果环境变量名称包含等于字符(=)或NUL字符,则此函数可能返回VarError,如果环境变量不是有效的Unicode字符,此函数将返回VarError。
is_ok() Returns `true` if the result is [`Ok`](https://doc.rust-lang.org/std/result/enum.Result.html#variant.Ok).
```rust
pub enum VarError {
NotPresent,
NotUnicode(OsString),
}
```
Powershell下的运行图:
![image-20230417144329323](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230417144329323.png)
移除环境变量:
![image-20230417144946971](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230417144946971.png)
搜索环境变量的顺序:
1. 当前进程的环境变量。
2. 用户环境变量。
3. 系统环境变量。
## 使用stderr代替stdout处理错误信息
在大多数终端中,有两种输出类型:标准输出(stdout)用于一般信息,标准错误(stderr)用于错误消息。这种区别使用户能够选择将程序的成功输出定向到文件,但仍将错误消息打印到屏幕上。println!宏只能打印到标准输出。
### 检查错误信息写入的位置
在代码中引发一个错误,查看内容显示的位置。为了演示这种行为,我们将使用>和我们想要将标准输出流重定向到的文件路径,即output.txt。我们不会传递任何参数,这应该会导致一个错误:
![image-20230417154823817](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230417154823817.png)
程序目前将错误信息也写入了文件,文件中应只存在运行成功的数据,故需进行更改。
### 使用stderr打印错误
标准库提供eprintln!宏,打印到标准错误流(stderr)。
错误情况:
![image-20230417155738766](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230417155738766.png)
正确情况:
![image-20230417155924280](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230417155924280.png)
一些额外知识:
![image-20230417161646756](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230417161646756.png)

@ -0,0 +1,426 @@
# 函数式语言特性:迭代器和闭包
Rust的设计灵感来自许多现有的语言和技术,其中一个重要的影响是**函数式编程**。函数式编程风格通常包括将**函数作为值**使用,方法包括在参数中传递它们、从其他函数返回它们、将它们分配给变量以供以后执行等等。
rust的主要特征与函数式的特性相似:
- 闭包(Closures):一种类似于函数的构造,可以存储在变量中。
- 迭代器(Iterators):一种处理一系列元素的方法。
- 如何使用闭包和迭代器来改进第 12 章中的 I/O 项目。
- 闭包和迭代器的性能(剧透:它们比你想象的要快!)。
其他的 Rust 特性,例如模式匹配和枚举类型,它们也受到了函数式编程风格的影响。闭包和迭代器是编写 Rust 代码的惯用方式,也是编写高效 Rust 代码的重要组成部分。
## 闭包:捕获环境的匿名函数
Rust的闭包是**匿名函数**,可以保存在变量中,也可以作为参数传递给其他函数。可以在一个地方创建闭包,然后再其他地方调用闭包,以便在不同上下文中计算。不同与函数,闭包从定义它们的**作用域**捕获值。
### 使用闭包捕获环境
如何使用闭包从定义它们的环境中捕获值,以供以后使用。
下面代码主要实现一个场景:t恤公司每隔一段时间就会向我们邮件列表上的某个人赠送一件独家限量版t恤作为促销活动。邮件列表中的用户可以选择将自己喜欢的颜色添加到个人资料中。如果选择免费衬衫的人有他们最喜欢的颜色,他们就会得到那件颜色的衬衫。如果这个人没有指定最喜欢的颜色,他们就会得到公司目前最常用的颜色。
```rust
#[derive(Debug, PartialEq, Copy, Clone)]
enum ShirtColor{
Red,
Blue,
}
struct Inventory{
shirts: Vec<ShirtColor>,
}
impl Inventory{
fn giveaway(&self, user_preference: Option<ShirtColor>) -> ShirtColor {
user_preference.unwrap_or_else(|| self.most_stocked())
}
fn most_stocked(&self) -> ShirtColor {
let mut num_red = 0;
let mut num_blue = 0;
for color in &self.shirts {
match color {
ShirtColor::Red => num_red += 1,
ShirtColor::Blue => num_blue += 1,
}
}
if num_red > num_blue {
ShirtColor::Red
}else {
ShirtColor::Blue
}
}
}
fn main() {
let store = Inventory{
shirts: vec![ShirtColor::Blue, ShirtColor::Red, ShirtColor::Blue],
};
let user_pref1 = Some(ShirtColor::Red);
let giveaway1 = store.giveaway(user_pref1);
println!(
"The user with preference {:?} get {:?}",
user_pref1, giveaway1
);
let user_pref2 = None;
let giveaway2 = store.giveaway(user_pref2);
println!(
"The user with preference {:?} get {:?}",
user_pref2, giveaway2
);
}
```
运行结果:
![image-20230418093942714](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230418093942714.png)
闭包代码:
```rust
user_preference.unwrap_or_else(|| self.most_stocked())
```
Option<T>类型的unwrap_or_else方法是由标准库定义的。它接受一个参数:一个没有参数并返回T类型值(在本例中是ShirtColor,与Option<T>中Some变体中存储的类型相同)的闭包。如果Option<T>是Some变体,则unwrap_or_else返回Some中的值。如果Option<T>是None变体,则unwrap_or_else调用闭包并返回闭包返回的值。
我们将闭包表达式|| self.most_stocked()指定为unwrap_or_else的参数。这是一个不带参数的闭包(如果闭包有参数,则它们将出现在两个竖杠之间)。闭包的主体调用self.most_stocked()。我们在这里定义了闭包,如果需要结果,unwrap_or_else的实现将稍后评估闭包。
这里我们传递了一个闭包,该闭包在当前Inventory实例上调用self.most_stocked()方法。标准库不需要了解我们定义的Inventory或ShirtColor类型,或我们想在此情况下使用的逻辑。闭包捕获了对self Inventory实例的不可变引用,并将其与我们指定的代码一起传递给unwrap_or_else方法。另一方面,函数不能以这种方式捕获其环境。
### 闭包类型推断和声明
函数和闭包之间有很多的差异。闭包通常不需要像函数那样声明参数或返回值的类型。函数需要类型声明是因为类型是暴露给用户的**显式接口**的一部分。闭包不像这样在公开的接口中使用:它们存储在变量中,而且在不将它们命名并公开给我们库的用户的情况下使用。
闭包通常很短,并且仅在狭窄的上下文中相关,而不是在任意的场景中。在这些有限的上下文中,编译器可以推断参数的**类型**和**返回类型**,类似于它能够推断**大多数变量**的类型(在极少数情况下,编译器也需要闭包类型注释)。
与变量一样,如果我们想要增加明确性和清晰度,我们可以添加类型声明,代价是比严格必要的更冗长。下面定义了一个对闭包**声明类型**并将其存储在一个**变量**中,不同与上面将闭包作为参数。
![image-20230418111424743](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230418111424743.png)
添加类型注释后,闭包语法与函数语法更相似。下面顶一个给形参加一的函数和与之相同行为的闭包代码:
```rust
fn add_one_v1(x: u32)-> u32{x + 1} // 函数定义
let add_one_v2 = |x: u32| -> u32 {x + 1}; // 带类型声明的闭包代码
let add_one_v3 = |x| {x + 1}; // 闭包代码(无类型声明)
let add_one_v4 = |x| x + 1; // 只有一个表达式,故可以删除括号
```
![image-20230418141236317](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230418141236317.png)
对于闭包定义,编译器将为它们的每个参数和返回值推断一个具体类型。下面显示了一个短闭包的定义,它只返回作为参数接收到的值。仅用来表示rust推断具体类型的过程。
![image-20230418145539072](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230418145539072.png)
切换二者顺序后:
![image-20230418145644369](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230418145644369.png)
### 捕获引用或移动所有权
闭包可以通过三种方式从环境中捕获值,这直接映射到**函数**获取参数的**三种方式**:**不可变地借用**、**可变地借用**和**获得所有权**。闭包将根据函数体对捕获的值所做的事情来决定使用其中的哪一个。
多个不可变借用:
![image-20230418153843851](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230418153843851.png)
可变地借用:
![image-20230418160635363](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230418160635363.png)
获得所有权:如果希望强制闭包获得它在环境中使用的值的所有权,可以在参数列表之前使用 **move** 关键字。当将闭包传递给新线程以移动数据以使其属于新线程时,这种技术非常有用。
![image-20230418162956631](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230418162956631.png)
若闭包主题只是打印列表,其实只需要一个不可变引用即可,但是因为生成了一个新线程,新线程可能在主线程的其他线程完成之前完成,或者主线程可能先完成。如果主线程保持列表的所有权,但在新线程完成之前结束并删除列表,则线程中的不可变引用将无效。所以需要在闭包定义的开头位置防止move关键字。主要就是为了**防止多线程下引用失效**也即新线程生命周期更长的情况。 故而move关键字不可缺少,同时因为转移了所有权在主线程也无法在使用list。
### 将捕获的值移出闭包和 Fn 特征
将捕获的引用或所有权从闭包定义的环境中捕获(从而影响是否移动到闭包中的任何内容),当闭包稍后被评估时,闭包体中的代码定义了引用或值的发生情况(从而影响是否移出闭包中的任何内容)。闭包体可以执行以下任何操作:**将捕获的值从闭包中移出**、**改变捕获的值**、**既不移动也不改变值**,或者从环境中**未捕获任何值**开始。
闭包捕获和处理环境中的值的方式影响闭包实现的特性,而特性是函数和结构体可以指定可以使用哪些类型的闭包的方式。闭包将以增加的方式自动实现以下这三种Fn特性中的**一种、两种或全部**,具体取决于闭包的体如何处理这些值:
- `Fn` 特质描述了一个闭包可以通过引用捕获外部变量的能力,这种捕获方式被称为“不可变借用捕获”,意味着闭包可以读取外部变量但不能修改它们。`Fn` 特质的闭包可以在其环境不可变时被调用,例如一个只读数据结构。
- `FnMut` 特质描述了一个闭包可以通过可变借用捕获外部变量的能力,这意味着闭包可以读取和修改它们。`FnMut` 特质的闭包可以在其环境可变时被调用,例如一个可变数据结构。
- `FnOnce` 特质描述了一个闭包可以通过移动语义(move semantics)将所有捕获的外部变量拥有并取走的能力,这意味着闭包可以获取它捕获的变量的所有权并对它们进行任意操作。`FnOnce` 特质的闭包只能被调用一次,之后它将拥有并占用它捕获的变量,不能再被调用。
```rust
impl<T> Option<T> {
pub fn unwrap_or_else<F>(self, f: F) -> T
where
F: FnOnce() -> T
{
match self {
Some(x) => x,
None => f(),
}
}
}
```
T是泛型类型,表示Option的Some变体中的值的类型。T类型也是unwrap_or_else函数的返回类型:例如,在Option<String>上调用unwrap_or_else函数的代码将得到一个String。
unwrap_or_else函数具有附加的泛型类型参数F。F类型是名为F的参数的类型,F是我们在调用unwrap_or_else时提供的闭包。
泛型类型F上指定的trait边界是FnOnce() -> T,这意味着F必须能够被调用一次,不接受参数,并返回T。在trait边界中使用FnOnce表示unwrap_or_else最多只调用F一次的约束。在unwrap_or_else函数体中,我们可以看到如果Option为Some,则不会调用f。如果Option为None, f将被调用一次。因为所有闭包都实现了FnOnce,所以unwrap_or_else可以接受大多数不同类型的闭包,并且尽可能地灵活。
##### 闭包(closure)和函数(function)之间的区别
闭包是一个**匿名函数**,函数也被视为一种**闭包**,它们可以被转换为一个实现了 Fn、FnMut 或 FnOnce 的闭包对象(**不需要配置任何东西,Rust 的标准库已经包含了 Fn、FnMut 和 FnOnce 特质的默认实现。只要你的函数签名符合这些特质的要求,你的函数就可以自动实现这些特质。**)。这意味着我们可以使用函数来代替闭包,只要函数的签名与所需的 **trait** 相匹配即可。Rust 中的闭包和函数都是可以执行代码的机制,但它们在一些方面有不同的限制和特性,需要**根据具体的情境选择合适的实现方式**。
例如,在一个 Option<Vec<T>> 值上,我们可以调用 unwrap_or_else(Vec::new) 来获取一个新的空向量,如果该值为 None 的话。
![image-20230419102100633](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230419102100633.png)
使用闭包实现:
![image-20230420090020479](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230420090020479.png)
当需要修改变量内容时,闭包和函数也有各自的实现方式:
![image-20230420091637609](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230420091637609.png)
###### sort_by_key FnMut特征
`sort_by_key` 实现了 `FnMut` 特征是因为**它会多次调用闭包,每次调用闭包时都会修改元素的顺序,因此需要能够修改闭包环境中的变量**。具体来说,闭包会在排序过程中根据键值来比较元素的顺序,而这个键值可能是通过对闭包环境中的变量进行一系列操作计算出来的,因此需要能够对闭包环境中的变量进行修改。
![image-20230420092436219](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230420092436219.png)
计算闭包执行次数:
![image-20230420100325150](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230420100325150.png)
## 迭代器
在 Rust 中,我们可以使用迭代器(iterator)处理集合中的每个元素。迭代器是一个实现了 **Iterator** trait 的对象,该 trait 提供了一些处理集合的方法,比如 map 和 filter。 Iterator trait 有一个 next 方法,该方法将迭代器的当前元素移动到下一个元素,返回一个 Option<T> 类型,其中 Some(T) 表示迭代器已经找到了下一个元素并将其返回,而 None 表示迭代器已经结束,没有更多元素可供迭代。迭代器是一种“**惰性**”的数据结构。
![image-20230420111555472](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230420111555472.png)
### **Iterator** trait 和 next()
**Iterator** trait 在标准库中。具体代码如下:
```rust
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// methods with default implementations elided
}
```
这个定义使用了一些新的语法:type Item 和 Self::Item,这些定义了一个带有关联类型的特征。故实现 Iterator 特征需要定义一个 Item 类型,而这个 Item 类型被用在 next 方法的返回类型中。换句话说,Item 类型将是迭代器返回的类型。
Iterator 特征只要求实现者定义一个方法:next 方法,它一次返回迭代器的一个元素,包装在 Some 中,当迭代结束时,返回 None。
![image-20230420115216269](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230420115216269.png)
请注意,我们需要将v1_iter设为**可变的**:在迭代器上调用next方法会更改迭代器**内部状态**,用于追踪它在序列中的位置。换句话说,这段代码会消耗掉(使用掉)迭代器,每次调用next都会消耗掉一个**迭代器项**。当我们使用for循环时,我们不需要将v1_iter设置为可变的,因为循环在**幕后获取了v1_iter的所有权并使其可变**。
另请注意,从对next的调用中获得的值是对向量中的**值**的**不可变引用**。**iter**方法生成一个**不可变引用的迭代器**。如果我们想要创建一个迭代器,它**获取v1的所有权**并返回所拥有的值,则可以调用**into_iter**而不是iter。同样,如果我们想要迭代**可变引用**,我们可以调用**iter_mut**而不是iter。
```rust
fn main() {
let mut v1 = vec![1, 2, 3];
// 创建一个可变引用迭代器
let mut v1_iter = v1.iter_mut();
for val in v1_iter{
*val += 1;
}
println!("v1: {:?}", v1); // v1: [2, 3, 4]
let v1 = vec![1, 2, 3]; // shadow
// 获取v1的所有权的迭代器
let v1_iter = v1.into_iter();
for val in v1_iter{
println!("Got: {}", val);
}
// println!("v1: {:?}", v1); // borrow of moved value: `v1`
}
```
### 使用迭代器的方法
Iterator特性有许多不同的[方法](https://doc.rust-lang.org/std/iter/trait.Iterator.html),由标准库提供默认实现。其中一些方法在其定义中调用**next方法**,这就是为什么在实现Iterator特性时需要实现next方法的原因。调用next的方法称为**消费适配器**,因为调用它们会耗尽迭代器。
```rust
#[test]
fn iterator_sum() {
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
let total: i32 = v1_iter.sum();
assert_eq!(total, 6);
// 在调用sum之后不允许使用v1_iter,因为sum将获得调用它的迭代器的所有权。
// assert_eq!(v1_iter.next(), None);
}
```
### 生成迭代器的方法
迭代器适配器是定义在Iterator trait上的不消耗迭代器的方法。相反,它们通过改变原始迭代器的某些方面来生成不同的迭代器。
**map**方法返回一个**新的迭代器**,用于生成修改后的项。这里的闭包创建了一个新的迭代器,其中vector中的每一项都加1:
![image-20230420151735714](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230420151735714.png)
为了修复这个警告并消费迭代器,我们将使用**collect**方法,此方法**使用迭代器并将结果值收集到集合数据类型中**。
![image-20230420153353776](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230420153353776.png)
`Vec<_>` 中的下划线是一个**占位符**,**表示编译器应该根据上下文自动推断出向量中元素的类型**。这种写法被称为类型占位符。因为已知类型 `v1` 向量的元素类型是 `i32`,故 `v2` 向量中的元素类型也是 `i32`。**可以将`Vec<_>` 写成`Vec<i32>`** 但如果我们将 `Vec<_>` 替换为 `Vec<i64>`,则会导致类型错误,因为 `i32` 类型和 `i64` 类型不匹配。
### 使用闭包捕获它们的环境
许多迭代器适配器将闭包作为参数,通常我们将指定作为迭代器适配器参数的闭包捕获其环境。在这个例子中,我们将使用`filter`方法,它接受一个闭包作为参数。这个闭包从迭代器获取一个元素,并返回一个布尔值。如果闭包返回`true`,则该值将包含在由`filter`生成的迭代中。如果闭包返回`false`,则该值不会被包含。
在代码中,将使用一个闭包来捕获`shoe_size`变量所在的环境,并用它来遍历一组`Shoe`结构的实例。这个闭包将只返回指定尺码的鞋子。
![image-20230420155944576](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230420155944576.png)
在函数体内部,我们首先使用`into_iter`方法将传入的向量转换成一个迭代器。然后,我们使用`filter`方法对这个迭代器进行适配,只保留鞋码与函数参数`shoe_size`相同的元素。这里的闭包捕获了`shoe_size`参数,并将其与每个鞋子的鞋码进行比较,以保留符合要求的鞋子。最后,我们调用`collect`方法将所有符合要求的元素收集起来,生成一个新的向量作为函数的返回值。
## 改进 I/O 项目
使用迭代器如何改进项目中Config::build函数和search函数的实现。
### 使用iterator替代clone
在12章中,我们添加了获取String值切片的代码,并通过索引切片和克隆值来创建Config结构体的实例,从而允许Config结构体拥有这些值。
```rust
pub struct Config {
pub query: String,
pub file_path: String,
pub ignore_case: bool,
}
impl Config {
pub fn build(args: &[String]) -> Result<Config, &'static str>{
if args.len() < 3{
return Err("not enough arguments");
}
let query = args[1].clone();
let file_path = args[2].clone();
let ignore_case = env::var("IGNORE_CASE").is_ok();
Ok(Config {query, file_path, ignore_case})
}
}
```
因为参数args类型为String数组的slice,所有权不在build()中。为了使Config实例获得所有权,对值调用clone方法来获得数据的完整副本。
修改构建函数,使其接受迭代器的所有权作为参数,而不是借用slice。我们将使用迭代器功能,而不是检查切片长度和索引到特定位置的代码。这将澄清Config::build函数在做什么,因为迭代器将访问这些值。一旦Config::build获得了迭代器的所有权并停止使用借位的索引操作,我们就可以将String值从迭代器移到Config中,而不是调用clone并进行新的分配。
#### Using the Returned Iterator Directly
首先修改main.rs中的文件
![image-20230420172223457](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230420172223457.png)
修改lib.rs代码:
![image-20230421092219406](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230421092219406.png)
env::args函数返回的迭代器类型是std::env::Args,它实现了Iterator trait并返回String值。Config::build函数的参数args的类型已经修改为impl Iterator<Item = String>,也就是实现了**Iterator trait并返回String值的类型**。这个修改使得参数args可以接收任何实现了Iterator trait并返回String值的类型作为输入,而不仅仅是原来的&[String]类型。
```rust
pub fn args() -> Args
//其中Args是一个结构体,实现了Iterator trait,并返回String类型的值。
```
### 使用迭代器适配器使代码更清晰
在I/O项目的搜索函数中利用迭代器
```rust
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let mut results = Vec::new();
for line in contents.lines(){
if line.contains(query){
results.push(line);
}
}
results
}
```
使用迭代器适配器方法来简化代码,并避免使用可变状态。函数式编程风格倾向于最小化可变状态的使用,以使代码更加清晰易懂。移除可变状态可以为将来的增强功能(如并行搜索)铺平道路,因为我们不必管理对results向量的并发访问。在多线程或并发编程中,多个线程或进程同时访问共享的数据结构或变量时,可能会出现数据不一致或竞态条件等问题。
```rust
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
contents.lines().filter(|line| line.contains(query)).collect()
}
```
### 迭代器与循环
循环(Loops)和迭代器(Iterators)是两种不同的控制结构,它们都用于对一个集合类型的元素进行遍历和处理,但是有一些区别。
循环是一种基本的控制结构,用于在程序中执行重复的操作,例如 `for` 循环和 `while` 循环等。迭代器是一种更加高级的控制结构,它提供了一种链式调用的方式来处理集合类型的元素。循环和迭代器的实现方式和使用场景有所不同,应该根据具体的需求选择更加合适的控制结构来处理集合类型的元素。在 Rust 中,许多 Rust 程序员更喜欢使用迭代器,因为它们可以更好地抽象出遍历和处理的高级目标,使得代码更加易于理解和维护。
## 性能比较:循环与迭代器
比较循环与迭代器的性能通常可以通过运行基准测试来进行。
闭包和迭代器是受到函数式编程语言思想启发的Rust特性。它们有助于Rust以低级性能清晰地表达高级思想的能力。闭包和迭代器的实现不会影响运行时性能。这是 Rust 努力提供**零成本抽象**的目标之一。

@ -0,0 +1,624 @@
# 14 关于 Cargo 和 Crates.io 的更多内容
[Cargo](https://doc.rust-lang.org/cargo/) 的一些更高级的功能:
- 通过发布配置文件自定义您的构建过程
- 在 crates.io 上发布库
- 使用工作区组织大型项目
- 从 crates.io 安装二进制文件
- 使用自定义命令扩展 Cargo
## 自定义release配置文件
在 Rust 中,发布配置是预定义的可自定义配置文件,具有不同的配置,允许程序员更好地控制编译代码的各种选项。每个配置文件都是独立配置的。
Cargo 有两个主要的配置文件:当运行 `cargo build` 时使用的 **dev** 配置文件和当运行 `cargo build --release` 时使用的 **release** 配置文件。dev 配置文件为开发环境提供了良好的默认设置,而 release 配置文件则为发布构建提供了良好的默认设置。
![image-20230421113935081](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230421113935081.png)
`dev``release` 是编译器使用的不同配置文件。Cargo 为每个配置文件都有默认设置,当项目的 `Cargo.toml` 文件中没有明确添加任何 `[profile.*]` 部分时,这些设置会**自动应用**。通过为您想要自定义的任何配置文件添加 `[profile.*]` 部分,可以覆盖默认设置的任何子集。例如,下面是 `dev``release` 配置文件中 `opt-level` 设置的默认值:
```rust
[profile.dev]
opt-level = 0
[profile.release]
opt-level = 3
```
覆盖`dev`默认设置:
![image-20230421161039703](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230421161039703.png)
### opt-level
`opt-level` 是 Rust 编译器中一个重要的优化选项,用于控制生成的代码的优化级别。`opt-level` 的值可以是 0 到 3,分别代表不同的优化级别:
- `opt-level = 0`:不进行优化。生成的代码更容易调试,但运行速度会变慢。
- `opt-level = 1`:启用一些基本的优化,例如删除不可达代码。生成的代码仍然可以很好地调试,但运行速度比 `opt-level = 0` 更快。
- `opt-level = 2`:启用更多的优化,例如内联函数和循环展开。生成的代码比 `opt-level = 1` 更快,但可能更难以调试。
- `opt-level = 3`:启用所有优化。生成的代码运行速度最快,但可能更难以调试。
`dev` 配置文件中,`opt-level` 的默认值为 `0`,这意味着生成的代码更容易调试。而在 `release` 配置文件中,`opt-level` 的默认值为 `3`,这意味着生成的代码运行速度最快,但可能更难以调试。可以根据具体情况,自定义不同的 `opt-level` 值,以平衡生成的代码的调试性能和运行速度。[相关文档。](https://doc.rust-lang.org/cargo/reference/profiles.html)
## 发布Crate到Crate.io
### 提供有用的文档注释
文档注释使用三个斜杠,///,而不是两个,并支持Markdown符号来格式化文本。将文档注释放在他们要记录的项目的前面。它将生成HTML文档。
![image-20230423094431419](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230423094431419.png)
生成 HTML 文档命令:
```rust
cargo doc // 从文档注释生成HTML文档。 生成位置:target/doc
cargo doc --open // 构建HTML,并在web浏览器中打开结果
```
HTML 界面:
![image-20230423101523963](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230423101523963.png)
#### 常用文档注释
- Panics:这个部分描述了函数在何种情况下会 panic。如果一个函数可能导致程序 panic,那么调用该函数的用户需要确保不会出现这些情况。
- Errors:如果一个函数返回一个 Result 类型,那么这个部分描述了可能发生的错误以及导致这些错误的条件。这有助于用户编写处理不同错误类型的代码。
- Safety:如果一个函数是不安全的,那么这个部分需要解释为什么这个函数是不安全的,并列出用户需要遵守的不变量。
这些部分不是必需的,但可以提醒编写者在文档注释中包含哪些信息,以便用户更好地理解代码。
另外,本段落提到在文档注释中添加示例代码块(Examples)的好处是可以帮助用户理解如何使用库,并且这些示例代码块也可以作为测试运行。在运行 `cargo test` 命令时,Rust 会在生成的文档中查找示例代码块,并将其作为测试运行,以确保示例代码的正确性。
```rust
/// Increment an integer by one.
///
/// # Examples
///
/// ```
/// let x = 5;
/// let result = add_one(x);
///
/// assert_eq!(result, 6);
/// ```
///
/// # Panics
///
/// This function will panic if the input value is `std::i32::MAX`.
///
/// # Errors
///
/// This function does not return an error.
///
/// # Safety
///
/// This function is safe to call with any valid `i32` value.
///
/// However, calling this function with a value of `std::i32::MAX` will result in a panic.
pub fn add_one(x: i32) -> i32 {
if x == std::i32::MAX {
panic!("Attempted to add one to i32::MAX");
} else {
x + 1
}
}
```
添加后的页面:
![image-20230423103424252](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230423103424252.png)
#### 使用文档注释进行测试
![image-20230423104049806](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230423104049806.png)
#### 为包含注释的项(即 crate 或 module)添加文档
`///` 注释不同,`//!` 注释不是针对特定项的,而是针对包含注释的项的。因此,`//!` 注释通常用于在 **crate****module** 级别上添加文档。在 crate root 文件(通常是 `src/lib.rs` 文件)或 module 内部使用 `//!` 注释,可以为整个 crate 或 module 添加一段**描述其功能、设计理念和使用方法**等信息的文档。
当使用 `cargo doc` 命令生成 Rust crate 的 API 文档时,它将解析源代码中的文档注释,并将其转换为 HTML 文档。在生成的文档中,`//!` 注释将出现在 crate 或 module 的文档页面上,提供关于 crate 或 module 的描述、使用示例和其他相关信息。
```rust
//! # examples
//!
//! `Examples` is a collection of utilities to make performing certain
//! calculations more convenient.
/// Adds one to the number given.
///
/// # Examples
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
x + 1
}
```
对比图:
![image-20230423113656778](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230423113656778.png)
### 使用 `pub use` 来重新导出公共项
当我们在开发一个 crate 时,我们可能会组织它的结构以方便我们自己使用,但是对于其他人来说,这可能并不方便。他们可能需要通过复杂的路径才能访问我们的类型、函数等。这时,我们可以使用 `pub use` 来重新导出项,以**创建一个公共 API**,使其他人更容易访问我们的类型、函数等。
```rust
//! # Art
//!
//! A library for modeling artistic concepts
pub mod kinds {
/// The primary colors according to the RYB color model.
pub enum PrimaryColor {
Red,
Yellow,
Blue,
}
/// The secondary colors according to the RYB color model.
pub enum SecondaryColor{
Orange,
Green,
Purple,
}
}
pub mod utils {
use crate::kinds::*;
/// Combines two primary colors in equal amounts to create
/// a secondary color.
pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor{
match (c1, c2) {
(PrimaryColor::Red, PrimaryColor::Yellow) |
(PrimaryColor::Yellow, PrimaryColor::Red) => SecondaryColor::Orange,
(PrimaryColor::Red, PrimaryColor::Blue) |
(PrimaryColor::Blue, PrimaryColor::Red) => SecondaryColor::Purple,
(PrimaryColor::Yellow, PrimaryColor::Blue) |
(PrimaryColor::Blue, PrimaryColor::Yellow) => SecondaryColor::Green,
(_, _) => SecondaryColor::Purple,
}
}
}
```
文档页面图:
![image-20230423135736510](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230423135736510.png)
目前PrimaryColor和SecondaryColor类型以及mix()没有在首页列出。下面是一个使用此crate的代码:
```rust
use art::kinds::PrimaryColor;
use art::utils::mix;
fn main() {
let red = PrimaryColor::Red;
let yellow = PrimaryColor::Yellow;
mix(red, yellow);
}
```
根据代码可见,使用PrimaryColor和mix必须知道他们所处的模块。为解决此问题可使用pub use 重新导出顶层的项目。
```rust
//! # Art
//!
//! A library for modeling artistic concepts
pub use self::kinds::PrimaryColor;
pub use self::kinds::SecondaryColor;
pub use self::utils::mix;
pub mod kinds {
/// The primary colors according to the RYB color model.
pub enum PrimaryColor {
Red,
Yellow,
Blue,
}
/// The secondary colors according to the RYB color model.
pub enum SecondaryColor{
Orange,
Green,
Purple,
}
}
pub mod utils {
use crate::kinds::*;
/// Combines two primary colors in equal amounts to create
/// a secondary color.
pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor{
match (c1, c2) {
(PrimaryColor::Red, PrimaryColor::Yellow) |
(PrimaryColor::Yellow, PrimaryColor::Red) => SecondaryColor::Orange,
(PrimaryColor::Red, PrimaryColor::Blue) |
(PrimaryColor::Blue, PrimaryColor::Red) => SecondaryColor::Purple,
(PrimaryColor::Yellow, PrimaryColor::Blue) |
(PrimaryColor::Blue, PrimaryColor::Yellow) => SecondaryColor::Green,
(_, _) => SecondaryColor::Purple,
}
}
}
```
文档页面图:
![image-20230423151005642](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230423151005642.png)
使用 Art crate代码:
```rust
use art::PrimaryColor;
use art::mix;
fn main() {
let blue = PrimaryColor::Blue;
let yellow = PrimaryColor::Yellow;
println!("{:#?}", mix(blue, yellow));
}
```
使用 `pub use` 可以将内部类型和函数重新导出到顶层,以提高代码的可读性和可用性。创建有用的公共 API 结构更多的是一种艺术而不是一门科学,需要进行多次迭代和测试以找到最佳的设计。
### 创建 crates.io 账号
在发布任何crate之前,您需要在[crate](https://crates.io/)上创建一个帐户,并获得一个API令牌。创建[链接](https://crates.io/settings/tokens)
![image-20230423154227662](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230423154227662.png)
[cargo-login](https://doc.rust-lang.org/cargo/commands/cargo-login.html) 可以将 API token 保存在本地,方便后续上传 crate 时进行身份验证。如果没有登录,上传 crate 时将会提示你登录。使用 `cargo login` 命令登录成功后,API token 将保存在 `$HOME/.cargo/credentials` 文件中。在执行 `cargo publish` 命令时,会自动使用该文件中保存的 API token 进行身份验证,以便将 crate 发布到 crates.io。
![image-20230423160145017](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230423160145017.png)
token位置:
![image-20230423160319545](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230423160319545.png)
### 发布crate
发布一个 crate 之前需要在 Cargo.toml 文件中的 [package] 部分添加元数据,并且 crate 必须拥有唯一的名称。在发布到 crates.io 上时需要**检查该名称是否已经被使用**,如果已被占用就需要选择另一个名称进行发布。当在本地开发时,可以随意命名。
```rust
cargo publish // 发布crate
```
![image-20230423163737104](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230423163737104.png)
发布是永久性的。版本永远不能被覆盖,代码也不能被删除。
### 发布crate新版本
当你对你的 crate 进行了更改并准备发布一个新版本时,你需要修改 Cargo.toml 文件中指定的**版本值**并重新发布。使用语义化版本控制规则来决定下一个适当的版本号,根据你所做的更改的种类。然后运行 `cargo publish` 命令来上传新版本。
![image-20230423165714253](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230423165714253.png)
### cargo yank 弃用 crate 版本
`cargo yank` 命令来弃用某个 crate 版本。弃用版本意味着防止未来的项目使用这个版本作为依赖,但所有现有项目仍然可以继续使用该版本。这在某些情况下很有用,比如某个 crate 版本存在 bug 或安全问题。使用 `cargo yank` 命令可以更新 Crates.io 的索引,并将指定版本标记为弃用状态。
要使用 `cargo yank` 命令弃用某个版本,你需要在之前已发布的 crate 目录下运行该命令,并指定要弃用的版本号。例如,如果要弃用名为 `zm-test` 版本号为 0.1.0 的 crate,需要在该 crate 目录下运行 `cargo yank --vers 0.1.0` 命令。如果需要重新启用已经弃用的版本,可以运行 `cargo yank --vers 0.1.0 --undo` 命令来撤销弃用状态。
需要注意的是,弃用版本不会删除任何代码,也不会防止已经使用该版本的项目出现问题,例如,如果一个版本中包含了误上传的密钥,那么即使使用 `cargo yank` 命令将该版本弃用,这些密钥仍然可能被泄露,因此需要立即重置这些密钥。
```rust
cargo yank --vers 0.1.0
Updating crates.io index
Yank zm-test@0.1.0
```
页面显示:
![image-20230424103251005](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230424103251005.png)
```rust
cargo yank --vers 0.1.0 --undo
Updating crates.io index
Yank zm-test@0.1.0
```
页面显示:
![image-20230424103512067](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230424103512067.png)
## Cargo 工作区
Cargo workspace,它是一个包含多个 Rust 包的集合。**这些包共享同一个 Cargo.lock 文件和输出目录**。在这个例子中,我们创建了一个包含一个二进制包和两个库包的 workspace。这个二进制包提供主要的功能,依赖于这两个库包。其中一个库包提供了 add_one 函数,另一个库包提供了 add_two 函数。所有这三个 crate(包)都是 workspace 的一部分。Cargo 通过使用单个顶层目录下的 target 目录来管理 workspace 中所有 crate 的编译输出。这样,当有 crate 依赖于另一个 crate 时,就可以避免不必要的重新编译。
首先新建一个工作区目录:
```rust
mkdir add
cd add
```
然后新建一个Cargo.toml文件配置整个工作区,只需有以下内容即可:
```rust
[workspace]
members = [
"adder",
]
```
在add目录中新建adder二进制crate。并使用cargo build构建工作区。现目录中文件如下所示:
```rust
├── Cargo.lock
├── Cargo.toml
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
```
工作区在顶层有一个target目录,将编译后的文件放入其中;adder包没有自己的目标目录。即使我们从adder目录内部运行cargo build,编译后的构件仍将在add/target而不是add/adder/target中。Cargo在工作区中按照这种方式**结构化目标目**录,因为工作区中的包应该相互依赖。
### 在工作区创建第二个包
首先修改最外层的 Cargo.toml, 添加add_one(新包名称)路径。
```rust
[workspace]
members = [
"adder",
"add_one",
]
```
新建 add_one lib crate。并使用cargo build:
```rust
├── Cargo.lock
├── Cargo.toml
├── add_one
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
```
在 add_one/src/lib.rs 中添加 add_one()
![image-20230424151704617](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230424151704617.png)
接下来让adder使用add_one包。首先修改adder/Cargo.toml文件。
![image-20230424161041199](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230424161041199.png)
Cargo并不假设工作区中的crate将相互依赖,因此我们需要明确依赖关系。在adder中使用add_one包方法,
![image-20230424163442014](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230424163442014.png)
#### **[cargo参数](https://doc.rust-lang.org/cargo/commands/cargo-run.html) -p 与 -bin**
`cargo run -p` 命令用于运行指定的包,而包通常包含一个或多个二进制文件。如果包只包含一个二进制文件,那么这个文件就是默认的二进制文件。`cargo run -p` 命令将运行指定包中的默认二进制文件。
如果包含多个二进制文件,可以通过 `cargo run -p` 命令的 `--bin` 选项来指定要运行的特定二进制文件。例如,`cargo run -p my_package --bin my_binary` 将运行 `my_package` 包中名为 `my_binary` 的二进制文件。
因此,`cargo run -p` 命令可以用于运行指定的包和该包中的默认或指定的二进制文件。
另一方面,`cargo run --bin` 命令用于运行指定包中的特定二进制文件。需要同时指定包名和要运行的二进制文件名。例如,`cargo run --bin my_binary --package my_package` 将运行名为 `my_package` 的包中名为 `my_binary` 的二进制文件。
因此,`cargo run --bin` 命令用于运行指定包中的特定二进制文件,而不管该包中是否包含其他二进制文件。
![image-20230424164918427](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230424164918427.png)
#### 依赖工作区外的包
工作区有一个顶级的 `Cargo.lock` 文件,而不是在每个 crate 的目录下都有一个 `Cargo.lock` 文件,这确保了所有 crate 都使用**相同版本**的所有依赖项。在 `add_one``adder` crate 的 `Cargo.toml` 文件中添加 `rand` 包作为依赖项,然后运行 `cargo build` 来构建整个 workspace,这样所有 crate 就可以使用相同版本的 `rand` 包了。虽然 `rand` 包已经在 workspace 中使用了,但是如果想在 workspace 中的其他 crate 中使用它,就必须在这些 crate 的 `Cargo.toml` 文件中也添加 `rand` 作为依赖项。
先在 add_one/Cargo.toml 添加 rand依赖
![image-20230424163205318](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230424163205318.png)
build后,工作区的 Cargo.lock 已经包含了 add_one 对rand的依赖的信息。
![image-20230424163124138](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230424163124138.png)
但若在其他 crate 中直接使用rand,是不可以的:
![image-20230424165906881](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230424165906881.png)
必须在 `Cargo.toml` 文件中也添加 `rand` 作为依赖项才可以使用:
![image-20230424170928013](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230424170928013.png)
不会下载rand的其他副本。Cargo已经确保工作空间中使用rand包的每个包中的每个crate都使用相同的版本,从而节省了我们的空间,并确保工作空间中的crate彼此兼容。
#### 在工作区中添加测试
当在工作区最外层运行 cargo test 时,会测试工作区所有的crate。
![image-20230424172215377](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230424172215377.png)
输出的第一部分显示通过了add_one crate中的it_works测试。下一节显示在adder crate中没有找到任何测试,最后一节显示在add_one crate中没有找到任何文档测试。
可通过 -p 指定要测试的crate:
![image-20230424172810426](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230424172810426.png)
#### 发布工作区crate
如果想将 workspace 中的 crate 发布到 crates.io 上,每个 crate 都需要**单独发布**,可以使用 `-p` 标志和要发布的 crate 的名称来发布特定的 crate。如果有多个 crate 需要发布,需要分别指定每个 crate 的名称并分别执行 `cargo publish -p <crate-name>` 命令。
## 使用cargo install安装可执行文件
[cargo install](https://doc.rust-lang.org/cargo/commands/cargo-install.html) 是用于本地安装和使用二进制 crate 的命令,不是用来替代系统包管理的。它适用于 Rust 开发者安装和使用别人在 crates.io 上分享的工具。注意,只有具有二进制目标的包才能被安装。二进制目标是指如果 crate 有 **src/main.rs** 文件或者指定了其他二进制文件,生成的**可执行程序**,而不是一个不能独立运行,但适合包含在其他程序中的库目标。
所有用 cargo install 安装的二进制程序都存储在安装根目录的 **bin** 文件夹中。如果你使用 rustup.rs 安装了 Rust 并且没有进行自定义配置,该目录将为 $HOME/.cargo/bin。确保该目录在 $PATH 中,以便能够运行使用 cargo install 安装的程序。
- `cargo install` 用于将某个 crate 的二进制文件安装到系统的二进制目录下,以便在命令行中直接调用该程序。通常用于安装 Rust 生态系统中提供的工具或第三方应用程序。
- `cargo run` 用于在开发阶段直接运行项目中的二进制文件,通常用于测试、调试和验证代码。
简而言之,`cargo run` 用于开发阶段直接运行代码,而 `cargo install` 则用于在生产环境中安装可执行文件。
install 远端crate(非本地程序,修改不了):
![image-20230425111309861](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230425111309861.png)
install 本地crate:
![image-20230425152141433](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230425152141433.png)
cargo install 操作 cargo install --force xx 用于覆盖crate:
![image-20230425153534566](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230425153534566.png)
## 自定义命令扩展Cargo
Cargo的设计使得你可以扩展它的子命令而不必修改Cargo本身。Cargo 可以通过添加自定义子命令进行扩展,只需在 $PATH 中添加名为 cargo-xxx 的二进制文件,即可通过 cargo xxx 命令运行。自定义命令还会在运行 cargo --list 时列出。通过 cargo install 安装扩展,并像内置 Cargo 工具一样运行。
![image-20230425161238033](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230425161238033.png)
cargo --list:
![image-20230425161337132](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230425161337132.png)
### 为自定义命令添加描述
目前还没有找到合适方法办法添加,测试了两种方法都失败了。第一种是修改Cargo.toml(失败)
```rust
[package]
name = "cargo-xxx"
version = "0.1.0"
[package.metadata.'cargo-xxx']
description = "My custom Cargo command"
```
第二种方式使用clap添加依赖(失败):
```rust
fn zm() -> App {
App::new("zm")
.about("描述")
.arg(Arg::with_name("file")
.help("The file to read")
.required(true)
.index(1))
}
```

File diff suppressed because it is too large Load Diff

@ -0,0 +1,93 @@
# 1590. 使数组和能被 P 整除
### description:
给你一个长度为 `n` 下标从 **0** 开始的字符串 `blocks` ,`blocks[i]` 要么是 `'W'` 要么是 `'B'` ,表示第 `i` 块的颜色。字符 `'W'``'B'` 分别表示白色和黑色。给你一个整数 `k` ,表示想要 **连续** 黑色块的数目。每一次操作中,你可以选择一个白色块将它 **涂成** 黑色块。请你返回至少出现 **一次** 连续 `k` 个黑色块的 **最少** 操作次数。
代码:
```rust
use std::collections::HashMap;
fn main() {
let arr:[i32; 4] = [3, 1, 4, 2];
let nums_vec:Vec<i32> = vec![3, 1, 4, 2];
let p = 6;
let res = min_subarray(&arr, p);
println!("res1 is: {}", res);
let res = min_subarray_vector(nums_vec, p);
println!("res2 is: {}", res);
}
fn min_subarray(nums:&[i32], p:i32) -> i32{
let mut x = 0;
for num in nums {
x = (x + num) % p;
}
if x == 0 {
return 0;
}
let mut index = HashMap::new();
// let mut y = 0;
// let mut res = arr.len();
let (mut y, mut res) = (0, nums.len() as i32);
for(i, num) in nums.iter().enumerate(){
// println!("index is {}, value is {}", i, num);
index.insert(y, i as i32);
y = (y + num) % p;
if let Some(&j) = index.get(&((y - x + p) % p)){
res = res.min((i as i32 - j) + 1);
}
}
if res == nums.len() as i32 {-1} else {res}
}
fn min_subarray_vector(nums:Vec<i32>, p:i32) -> i32{
let mut x = 0;
for num in &nums {
x = (x + num) % p;
}
if x == 0 {
return 0;
}
let mut index = HashMap::new();
let (mut y, mut res) = (0, nums.len() as i32);
for(i, num) in nums.iter().enumerate(){
// println!("index is {}, value is {}", i, num);
index.insert(y, i as i32);
y = (y + num) % p;
if let Some(&j) = index.get(&((y - x + p) % p)){
res = res.min((i as i32 - j) + 1);
}
}
// for num in &nums {
// println!("{}", num);
// }
if res == nums.len() as i32 {-1} else {res}
}
```
![image-20230310114754062](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310114754062.png)
`iter().enumerate()` 方法来同时遍历向量和获取下标。这个方法会返回一个元组 `(usize, &T)`,其中第一个元素是下标,第二个元素是对应的元素引用。不会失去所有权。
c++ 代码:
![image-20230310103201738](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230310103201738.png)

@ -0,0 +1,153 @@
# 1605. 给定行和列的和求可行矩阵
### description:
给你两个非负整数数组 `rowSum``colSum` ,其中 `rowSum[i]` 是二维矩阵中第 `i` 行元素的和, `colSum[j]` 是第 `j` 列元素的和。换言之你不知道矩阵里的每个元素,但是你知道每一行和每一列的和。请找到大小为 `rowSum.length x colSum.length` 的任意 **非负整数** 矩阵,且该矩阵满足 `rowSum``colSum` 的要求。请你返回任意一个满足题目要求的二维矩阵,题目保证存在 **至少一个** 可行矩阵。
代码: restore_matrix为正确写法。
````rust
use std::cmp;
fn main() {
let mut row_sum:Vec<i32> = vec![5, 7, 10];
let mut col_sum = vec![8, 6, 8];
// let matrix = restore_matrix(&mut row_sum.clone(), &mut col_sum);
let matrix = restore_matrix2(row_sum.clone(), col_sum); // col_sum 失去所有权,下面无法使用。row_sum可以使用。
for i in matrix{
for j in i{
print!("{} ", j);
}
println!();
}
println!(" - - - - - ");
for &i in &row_sum{
print!("{} ", i);
// print!("{} ", i==0); //用于判断类型。是&i32还是i32
}
// println!();
// for &i in &col_sum{
// print!("{} ", i);
// }
}
fn restore_matrix(row_sum:&mut [i32], col_sum:&mut [i32]) -> Vec<Vec<i32>> {
// let (n, m) = (row_sum.len() as i32, col_sum.len() as i32); // len()返回usize
// let (i, j) = (0, 0);
let (n, m) = (row_sum.len(), col_sum.len()); // len()返回usize
let (mut i, mut j) = (0usize, 0usize);
let mut matrix = vec![vec![0; m]; n]; //这里正好需要usize
while i < n && j < m {
let v = cmp::min(row_sum[i], col_sum[j]);
matrix[i][j] = v;
row_sum[i] -= v;
col_sum[j] -= v;
if 0==row_sum[i] {
i+=1;
}
if 0==col_sum[j] {
j+=1;
}
}
matrix
}
fn restore_matrix2(mut row_sum: Vec<i32>, mut col_sum: Vec<i32>) -> Vec<Vec<i32>> {
// let (n, m) = (row_sum.len() as i32, col_sum.len() as i32); // len()返回usize
// let (i, j) = (0, 0);
let (n, m) = (row_sum.len(), col_sum.len()); // len()返回usize
let (mut i, mut j) = (0usize, 0usize);
let mut matrix = vec![vec![0; m]; n]; //这里正好需要usize
while i < n && j < m {
let v = cmp::min(row_sum[i], col_sum[j]);
matrix[i][j] = v;
row_sum[i] -= v;
col_sum[j] -= v;
if 0==row_sum[i] {
i+=1;
}
if 0==col_sum[j] {
j+=1;
}
}
matrix
}
````
图:
![image-20230314113447242](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230314113447242.png)
c++代码:
```c++
#include <iostream>
#include <vector>
class Solution{
public:
std::vector<std::vector<int>> restoreMatrix(std::vector<int>& rowSum, std::vector<int>& colSum){
int n = rowSum.size(), m = colSum.size();
std::cout << n <<std::endl;
std::cout << m <<std::endl;
std::cout << "- - -" <<std::endl;
std::vector<std::vector<int>> matrix(n, std::vector<int>(m, 0));
int i = 0, j = 0;
while (i < n && j < m){
int v = std::min(rowSum[i], colSum[j]);
matrix[i][j] = v;
rowSum[i] -= v;
colSum[j] -= v;
if(0==rowSum[i]){
++i;
}
if(0==colSum[j]){
++j;
}
}
return matrix;
}
};
int main() {
std::vector<int> rowSum;
std::vector<int> colSum;
rowSum.push_back(5); rowSum.push_back(7); rowSum.push_back(10);
colSum.push_back(8); colSum.push_back(6); colSum.push_back(8);
Solution *so = new Solution();
std::vector<std::vector<int>> res = so ->restoreMatrix(rowSum, colSum);
std::vector<int> vecTmp;
for(std::vector<std::vector<int>>::iterator iter = res.begin(); iter != res.end(); iter++){
vecTmp = *iter;
for(std::vector<int>::iterator it = vecTmp.begin(); it != vecTmp.end(); it++){
std::cout<< *it << " ";
}
std::cout<< std::endl;
}
std::cout << " - - - " <<std::endl;
int i, j;
for (i = 0; i < res.size(); i++)
{
for(j = 0; j < res[0].size(); j++)
std::cout << res[i][j] << " ";
std::cout << std::endl;
}
return 0;
}
```

@ -0,0 +1,172 @@
# 1615. 最大网络秩
### description:
`n` 座城市和一些连接这些城市的道路 `roads` 共同组成一个基础设施网络。每个 `roads[i] = [ai, bi]` 都表示在城市 `ai``bi` 之间有一条双向道路。两座不同城市构成的 **城市对****网络秩** 定义为:与这两座城市 **直接** 相连的道路总数。如果存在一条道路直接连接这两座城市,则这条道路只计算 **一次** 。整个基础设施网络的 **最大网络秩** 是所有不同城市对中的 **最大网络秩** 。给你整数 `n` 和数组 `roads`,返回整个基础设施网络的 **最大网络秩**
代码:
```rust
fn main() {
let n:usize = 4;
let roads:Vec<Vec<i32>> = vec![vec![0,1], vec![0,3],vec![1,2], vec![1,3]];
let res = maximal_network_rank(n, &roads);
println!("res is: {}", res);
// for road in &roads{
// for &i in road {
// print!(" {} ", i == 2);
// }
// println!(" ");
// }
println!(" - - - ");
let roads:Vec<&[i32]> = vec![&[0,1], &[0,3], &[1,2], &[1,3]];
let res = maximal_network_rank2(n, &roads);
println!("res is: {}", res);
// for road in &roads{
// for &i in *road{
// print!(" {} ", i==2);
// }
// println!(" ");
// }
// 多维向量
let roads2:Vec<Vec<Vec<i32>>> = vec![vec![vec![0,1], vec![0,3],vec![1,2], vec![1,3]],vec![vec![0,1], vec![0,3],vec![1,2], vec![1,3]]];
for roads in &roads2{
for road in roads{
for &i in road {
print!(" {} ", i == 2);
}
println!(" ");
}
println!(" ");
}
// 多维数组
let roads3: &[&[&[i32]]] = &[&[&[0,1], &[0,3], &[1,2], &[1,3]], &[&[0,1], &[0,3], &[1,2], &[1,3]]];
for &roads in roads3{
for &road in roads{
for &i in road{
print!(" {} ", i==2);
}
}
println!(" ");
}
for roads in roads3{
for road in *roads{
for &i in *road{
print!(" {} ", i==2);
}
}
println!(" ");
}
// 多维数组
let roads4: Vec<&[&[i32]]> = vec![&[&[0,1], &[0,3], &[1,2], &[1,3]], &[&[0,1], &[0,3], &[1,2], &[1,3]]];
for roads in &roads4{
for road in *roads{
for &i in *road{
print!(" {} ", i==2);
}
}
println!(" ");
}
}
fn maximal_network_rank(n:usize, roads:&Vec<Vec<i32>>)->i32{
let mut connect:Vec<Vec<bool>> = vec![vec![false; n]; n];
let mut degree:Vec<i32> = vec![0; n];
for road in roads{
connect[road[0] as usize][road[1] as usize] = true;
connect[road[1] as usize][road[0] as usize] = true;
degree[road[0] as usize] = degree[road[0] as usize]+1;
degree[road[1] as usize] = degree[road[1] as usize]+1;
}
let mut max_rank = 0;
for i in 0..n {
for j in i+1..n{
let rank = degree[i] + degree[j] - (if connect[i][j] { 1 } else { 0 });
max_rank = max_rank.max(rank);
}
}
max_rank
}
fn maximal_network_rank2(n:usize, roads:&[&[i32]])->i32{
let mut connect:Vec<Vec<bool>> = vec![vec![false; n]; n];
let mut degree:Vec<i32> = vec![0; n];
for road in roads{
connect[road[0] as usize][road[1] as usize] = true;
connect[road[1] as usize][road[0] as usize] = true;
degree[road[0] as usize] += 1;
degree[road[1] as usize] += 1;
}
let mut max_rank = 0;
for i in 0..n {
for j in i+1..n{
let rank = degree[i] + degree[j] - if connect[i][j] { 1 } else { 0 };
max_rank = max_rank.max(rank);
}
}
max_rank
}
```
先这样理解,以后遇到再说把,有点乱。
![image-20230315153441708](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230315153441708.png)
图:
![image-20230315095347362](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230315095347362.png)
接收任何 `i32` 类型的切片,而不仅仅是 `Vec<i32>`写法。
![image-20230315110149054](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230315110149054.png)
关于for
![image-20230315110830336](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230315110830336.png)
遍历二维向量(不失去所有权的前提下,取出i32),多维也是如此。
![image-20230315112600727](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230315112600727.png)
多维:
![image-20230315113114677](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230315113114677.png)
`i32` implements [`Add<&i32>`](https://doc.rust-lang.org/std/primitive.i32.html#impl-Add<%26i32>-for-i32), the trait that implements the logic for the `+` operator (and other traits implementing other mathematical operators like `+=` and `*`, see [`std::ops`](https://doc.rust-lang.org/std/ops/index.html) module), but not `PartialEq<&i32>` (the trait used by comparison operators like `>`). Only [`PartialEq`](https://doc.rust-lang.org/std/primitive.i32.html#impl-Add<%26i32>-for-i32) is implemented for `i32`. So you can add, add assign and multiply, etc. `&i32` to a `i32` value, but you can not compare `&i32` with `i32`.
![image-20230315171720477](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230315171720477.png)
`for road in &roads` 中,`road` 的类型是 `&&[i32]`,是指向 `&[i32]` 类型的引用。这个引用是从 `roads``Vec` 中获取的。
`for road in roads` 中,`road` 的类型是 `&[i32]`,是 `roads` 中元素的引用,类型为 `&[i32]`。这里的 `roads``Vec<&[i32]>` 类型的引用,也就是指向 `&[i32]` 类型的引用的引用,即 `&&[i32]` 类型的引用。通过使用 `&``roads` 进行解引用,可以得到指向 `Vec<&[i32]>>` 类型的引用,即 `&Vec<&[i32]>>`,然后就可以对其进行迭代获取每个 `&[i32]` 类型的引用了。
在 Rust 中,数组或向量的元素可以通过索引访问,无论是使用 `[]` 运算符还是 `get()` 方法。当使用 `[]` 运算符时,Rust 会自动解引用数组或向量的引用,并将其作为指向第一个元素的指针来处理。因此,无论是 `for road in &roads` 还是 `for road in roads`,在使用 `road[0]` 访问数组或向量的第一个元素时,都将自动解引用指向该元素的指针,返回一个 `i32` 类型的值。这是 Rust 语言的自动解引用功能的一部分,它使得代码更加简洁和易读。

@ -0,0 +1,90 @@
# 1626. 无矛盾的最佳球队
### description:
假设你是球队的经理。对于即将到来的锦标赛,你想组合一支总体得分最高的球队。球队的得分是球队中所有球员的分数 **总和** 。然而,球队中的矛盾会限制球员的发挥,所以必须选出一支 **没有矛盾** 的球队。如果一名年龄较小球员的分数 **严格大于** 一名年龄较大的球员,则存在矛盾。同龄球员之间不会发生矛盾。给你两个列表 `scores``ages`,其中每组 `scores[i]``ages[i]` 表示第 `i` 名球员的分数和年龄。请你返回 **所有可能的无矛盾球队中得分最高那支的分数**
代码:
```rust
use std::cmp;
fn main() {
let a = vec![1, 2, 3, 5];
let b = vec![8, 9, 10, 1];
let res = best_team_score(&a, &b);
println!("res is: {}", res);
let mut vec = Vec::with_capacity(5); // 每次满了后 capacity *= 2
// The vector contains no items, even though it has capacity for more
println!("vec.len() is: {}, vec.capacity() is: {}", vec.len(), vec.capacity());
// These are all done without reallocating...
for i in 0..10 {
vec.push(i);
println!("vec.len() is: {}, vec.capacity() is: {}", vec.len(), vec.capacity());
}
println!("vec.len() is: {}, vec.capacity() is: {}", vec.len(), vec.capacity());
// ...but this may make the vector reallocate
vec.push(11);
println!("vec.len() is: {}, vec.capacity() is: {}", vec.len(), vec.capacity());
// A vector of a zero-sized type will always over-allocate, since no
// allocation is necessary
let vec_units = Vec::<()>::with_capacity(10);
println!("vec.len() is: {}, vec.capacity() is: {}", vec_units.len(), vec_units.capacity());
}
fn best_team_score(scores: &[i32], ages: &[i32]) -> i32{
let n = scores.len();
let mut people: Vec<(i32, i32)> = Vec::with_capacity(n);
for i in 0..n {
people.push((scores[i], ages[i]));
}
people.sort();
let mut dp: Vec<i32> = vec![0; n];
let mut res = 0;
for i in 0..n {
for j in (0..i).rev() {
if people[j].1 <= people[i].1 {
dp[i] = cmp::max(dp[i], dp[j]);
// dp[i] = dp[i].max(dp[j]);
}
}
dp[i] += people[i].0;
res = cmp::max(res, dp[i]);
}
res
}
```
```c++
class Solution {
public:
int bestTeamScore(vector<int>& scores, vector<int>& ages) {
int n = scores.size();
vector<pair<int, int>> people;
for (int i = 0; i < n; ++i) {
people.push_back({scores[i], ages[i]});
}
sort(people.begin(), people.end());
vector<int> dp(n, 0);
int res = 0;
for (int i = 0; i < n; ++i) {
for (int j = i - 1; j >= 0; --j) {
if (people[j].second <= people[i].second) {
dp[i] = max(dp[i], dp[j]);
}
}
dp[i] += people[i].first;
res = max(res, dp[i]);
}
return res;
}
};
```

@ -0,0 +1,558 @@
## 并发
Rust 的主要目标之一是处理并发编程,保证程序的内存安全和高效运行。通过利用 Rust 的所有权和类型检查系统,使许多并发错误可以在**编译时**检测到,从而避免了**运行时**错误。Rust 的 **无畏并发**(`fearless concurrency`) 使得编写代码变得更加容易,而且易于重构,不会引入新的错误。
### 使用线程同时运行代码
Rust 中的线程(threads)是用于同时运行程序中独立部分的功能。在多线程上下文中编程需要仔细思考,因为线程可以同时运行,所以不同线程上代码的运行顺序无法预知,可能会导致问题,例如竞争状态(线程按照不一致的顺序访问数据或资源)、死锁(两个线程彼此等待,阻止两个线程继续执行)或 bug。通过使用 Rust 的线程实现模型,可以减轻使用线程的负面影响。Rust 的标准库使用了一种 1:1 的线程实现模型,即程序使用一个操作系统线程来支持一个语言线程。此外,还有其他线程模型的 crates,这些线程模型做出了与 1:1 模型不同的权衡。
**绿色线程与OS线程**
`绿色线程`是由语言运行时或编程语言本身实现的线程,而`系统线程`则是由操作系统内核提供的线程。在使用`绿色线程`时,通常需要将它们映射到`系统线程`上执行。具体来说,这些绿色线程通常在运行时的线程池中运行,线程池由少量的`系统线程`负责管理,这些`系统线程`负责调度和执行`绿色线程`。
`绿色线程`模式被称为 M:N 模型,其中 M 个`绿色线程`对应 N 个`操作系统`线程,这里 M 和 N 不必相同。
#### 使用 spawn 创建一个线程
创建一个新线程,我们需要调用 `thread::spawn` 函数并传递一个闭包,包含我们想要在新线程中运行的代码。
```rust
use std::thread;
use std::time::Duration;
fn main() {
thread::spawn(|| {
for i in 1..10 {
println!("hi number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});
for i in 0..5 {
println!("hi number {} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}
}
```
当 Rust 程序的主线程完成时,无论它们是否已完成运行,所有生成的线程都会被关闭,故线程并没有打印到9。输出结果类似下图:
![image-20230511170548126](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230511170548126.png)
扩大主函数循环次数后,运行图:
![image-20230511172627602](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230511172627602.png)
`thread::sleep`函数是Rust标准库中的一个函数,用于使当前线程休眠(暂停执行)一段时间,以便让其他线程有机会运行。它接受一个时间参数,以指定休眠的时间长度,可以使用`Duration`类型或者`std::time::Duration`模块的方法来指定休眠时间。在多线程编程中,使用`thread::sleep`函数可以帮助我们控制线程执行的顺序和时间片分配,从而更好地利用系统资源。但需要注意的是,使用`thread::sleep`并不能保证线程的执行顺序,这取决于操作系统的调度算法和硬件资源的可用情况。`thread::sleep`函数是阻塞的,不应该在async函数(异步函数)中使用。
#### join 等待所有线程完成
为防止主线程结束时,子线程未运行完成问题。使用`join`方法,等待线程完成。
![image-20230512093253398](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230512093253398.png)
添加join方法后,主函数会在for运行后,等待子线程运行完毕。
移动join代码位置(join调用放在主线程的for循环之前):
![image-20230512095104846](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230512095104846.png)
主线程将等待子线程完成,然后运行其for循环,因此输出将不再交错。
#### 在线程中使用 move 闭包
`move` 关键字,使闭包获得它使用的值的所有权,可通过 `move` 将值的所有权从一个线程转移到另一个线程。
当子线程中使用主线程数据,但未使用 `move` 关键字:
```rust
use std::thread;
fn main() {
let v = vec![1, 2, 3];
let handle = thread::spawn(|| {
println!("Here's a vector: {:?}", v);
});
handle.join().unwrap();
}
```
运行图:
![image-20230512103423445](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230512103423445.png)
错误信息:
```rust
error[E0373]: closure may outlive the current function, but it borrows `v`, which is owned by the current function
--> src\main.rs:5:32
|
5 | let handle = thread::spawn(|| {
| ^^ may outlive borrowed value `v`
6 | println!("Here's a vector: {:?}", v);
| - `v` is borrowed here
|
note: function requires argument type to outlive `'static`
--> src\main.rs:5:18
|
5 | let handle = thread::spawn(|| {
| __________________^
6 | | println!("Here's a vector: {:?}", v);
7 | | });
| |______^
help: to force the closure to take ownership of `v` (and any other referenced variables), use the `move` keyword
|
5 | let handle = thread::spawn(move || {
| ++++
For more information about this error, try `rustc --explain E0373`.
error: could not compile `move_thread` due to previous error
```
因为 Rust 不知道这个新建线程会执行多久,所以无法知晓 `v` 的引用是否一直有效。例如以下情况:
```rust
use std::thread;
fn main() {
let v = vec![1, 2, 3];
let handle = thread::spawn(|| {
println!("Here's a vector: {:?}", v);
});
drop(v); // oh no!
handle.join().unwrap();
}
```
根据错误提示修改代码:
```
help: to force the closure to take ownership of `v` (and any other referenced variables), use the `move` keyword
|
5 | let handle = thread::spawn(move || {
```
使用 `move` 将所有权转移至闭包中:
```rust
use std::thread;
fn main() {
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("Here's a vector: {:?}", v);
});
// drop(v); // error: value used here after move
handle.join().unwrap();
}
```
运行图:
![image-20230512110106611](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230512110106611.png)
## 消息传递(*message passing*):在线程中传递信息
`通道`是一种保证并发安全的消息传递机制,其核心思想是通过`消息传递`来共享内存,而不是通过共享内存来进行通信。可以把`通道`看作一个水道,其中有`发送器`和`接收器`两个部分。发送器可以向通道中发送数据,而接收器可以从通道中接收数据。通道的一个重要特点是可以保证线程之间的`同步`,避免了数据竞争的问题。通道不仅可以用于`线程`间通信,还可以用于不同`进程`之间的通信。同时,通道的使用也可以避免一些并发编程中常见的陷阱和错误。
```rust
use std::sync::mpsc;
fn main() {
let (tx, rx) = mpsc::channel();
// 不可编译,因未做任何事,不知 通道 发送类型
// error: cannot infer type of the type parameter `T` declared on the function `channel`
}
```
`pub fn channel<T>() -> (Sender<T>, Receiver<T>)` 创建一个新的异步通道,并返回发送端(`tx`)和接收端(`rx`)。`通道的实现方式允许一个通道有多个发送端,但只能有一个接收端`。当至少有一个发送端存在(包括克隆的发送端),`recv`操作将阻塞,直到有可用的消息。`try_recv`不会阻塞,会立即返回`Result<T,E>`:有消息则返回一个`Ok`值,没有消息则返回一个`Err`值。
如果在使用发送端进行发送时接收端已经断开连接,`send`方法将返回一个`SendError`。类似地,如果在接收消息时发送端已经断开连接,`recv`方法将返回一个`RecvError`。
```rust
use std::sync::mpsc;
use std::thread;
use std::sync::mpsc::RecvError;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let val = String::from("hi");
tx.send(val).unwrap()
});
let receive = rx.recv().unwrap();
println!("Got: {}", receive);
assert_eq!(Err(RecvError), rx.recv());
}
```
### 通道与所有权转移
并发编程中因所有权的存在会避免很多错误的产生。以下为错误案例(原因为:转移变量所有权后仍使用变量):
```rust
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let val = String::from("hi");
tx.send(val).unwrap();
println!("val is {}", val); // 不可继续使用val
});
let received = rx.recv().unwrap();
println!("Got: {}", received);
}
```
错误信息:
```rust
error[E0382]: borrow of moved value: `val`
--> src\main.rs:10:31
|
8 | let val = String::from("hi");
| --- move occurs because `val` has type `String`, which does not implement the `Copy` trait
9 | tx.send(val).unwrap();
| --- value moved here
10 | println!("val is {}", val);
| ^^^ value borrowed here after move
```
### 发送多个值
为证明并发性,使线程发送多个消息,并在每个消息之间暂停一秒钟。以观察到两个线程在通道上进行交互的情况。
```rust
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let vals = vec![
String::from("hi"),
String::from("from"),
String::from("the"),
String::from("thread"),
];
for val in vals {
tx.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
for received in rx {
println!("Got: {}", received);
}
}
```
![image-20230608114743104](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230608114743104.png)
主线程中,不再显式调用 `recv` 函数,将 rx 视为一个**迭代器**来读取值。因为`for`循环中没有任何暂停或延迟的代码,可知主线程正等待从线程接收值。
### 通过克隆创建多个发送端
mpsc是多生产者、单消费者的缩写。
```rust
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
let (tx, rx) = mpsc::channel();
let tx1 = tx.clone();
thread::spawn(move || {
let vals = vec![
String::from("hi"),
String::from("from"),
String::from("the"),
String::from("thread"),
String::from("thread2"),
String::from("thread3"),
];
for val in vals {
tx1.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
thread::spawn(move || {
let vals = vec![
String::from("more"),
String::from("messages"),
String::from("for"),
String::from("you"),
];
for val in vals{
tx.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
for received in rx {
println!("Got: {}", received);
}
}
```
调用`clone`函数来克隆发送端。获得一个新的发送端,将其传递给第一个线程。同时将原始的发送端传递给第二个生成的线程。这样,我们就创建了两个线程,每个线程都向同一个接收者发送不同的消息。
因为并发性,输出的顺序可能会有所不同,这取决于系统。当在不同的线程中使用不同的`thread::sleep`值进行时,每次运行的结果将更加不确定。
![image-20230608172654544](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230608172654544.png)
## 共享状态并发性
编程语言中的通道(channels)类似于单一所有权(single ownership),因为一旦将值传输到通道中,就无法再使用该值。而共享内存并发性则类似于多重所有权(multiple ownership):多个线程可以同时访问相同的内存位置。
### 使用互斥锁实现逐个线程访问数据
互斥锁(Mutex)是互斥(mutual exclusion)的缩写,它在任何给定时间只允许一个线程访问某些数据。要访问互斥锁中的数据,线程必须首先发出请求以获取互斥锁的锁。锁是互斥锁的一部分,它是一个数据结构,用于跟踪当前谁拥有对数据的独占访问权限。因此,互斥锁通过锁定系统,保护其持有的数据。
互斥锁的两个规则:
1. 在使用数据之前,必须尝试获取锁。
2. 在使用完互斥锁保护的数据后,必须解锁数据,以便其他线程可以获取锁。
### Mutex<T> API
单线程环境中使用互斥锁:
```rust
use std::sync::Mutex;
fn main() {
let m = Mutex::new(5);
{
let mut num = m.lock().unwrap();
*num = 6;
}
println!("m = {:?}", m);
}
```
要访问互斥锁内部的数据,需使用`lock`方法获取锁。这个调用会阻塞当前线程,直到获得锁为止。如果在调用`lock`时,有另一个线程持有了锁并发生了panic,那么当前线程无法获取到锁。为了处理这种情况,代码中选择使用`unwrap`函数来解包(unwrap)`LockResult`类型,如果发生了上述情况,当前线程会发生恐慌。
因为`m`的类型是`Mutex<i32>`,而不是直接的`i32`类型,因此我们必须调用`lock`方法来获取对`i32`值的访问权限。这是为了确保在使用`m`中的值之前先获取到了锁。如果我们忘记调用`lock`,类型系统将不允许我们访问内部的`i32`值。`Mutex<T>`实际上是一个智能指针。当我们调用`lock`方法时,它返回一个叫做`MutexGuard`的智能指针,它包装了被互斥锁保护的数据。`MutexGuard`实现了`Deref` trait,使得我们可以通过它访问内部数据。此外,`MutexGuard`还实现了`Drop` trait,在`MutexGuard`超出作用域时自动释放锁,这发生在内部作用域的末尾。这样一来,我们就不会遗忘手动释放锁,从而阻止其他线程使用互斥锁,因为锁的释放是自动进行的。
![image-20230628093116722](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230628093116722.png)
### `Mutex<T>`在多个线程之间共享一个值
创建10个线程,让它们每个线程将计数器的值增加1,使计数器从0增加到10。(代码无法通过编译)
```rust
use std::sync::Mutex;
use std::thread;
fn main() {
let counter = Mutex::new(0);
let mut handles = vec![];
for _ in 0..10 {
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}
```
错误信息:
```rust
error[E0382]: use of moved value: `counter`
--> src\main.rs:9:36
|
5 | let counter = Mutex::new(0);
| ------- move occurs because `counter` has type `Mutex<i32>`, which does not implement the `Copy` trait
...
9 | let handle = thread::spawn(move || {
| ^^^^^^^ value moved into closure here, in previous iteration of loop
10 | let mut num = counter.lock().unwrap();
| ------- use occurs due to use in closure
For more information about this error, try `rustc --explain E0382`.
```
因线程是同时运行的,并且它们在获取锁和修改计数器值的时候没有进行同步。这就导致了数据竞争,使得编译器无法保证计数器的值在运行时是正确的。
要解决这个问题,需要在多线程中正确地使用互斥锁。可以通过引入一个`Arc`(原子引用计数)来解决问题。`Arc`允许多个线程共享同一个计数器,而不会造成数据竞争。然后,我们在每个线程中使用`Arc`的克隆来处理计数器。
![image-20230728151937984](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230728151937984.png)
### 多线程和多重所有权
通过使用智能指针`Rc<T>`创建引用计数值来使一个值拥有多个所有者。使用`Rc<T>`将`Mutex<T>`进行包装,并在将所有权移交给线程之前克隆`Rc<T>`
```rust
use std::sync::Mutex;
use std::rc::Rc;
use std::thread;
fn main() {
let counter = Rc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Rc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}
```
错误信息:
```rust
error[E0277]: `Rc<Mutex<i32>>` cannot be sent between threads safely
--> src\main.rs:10:36
|
10 | let handle = thread::spawn(move || {
| ------------- ^------
| | |
| ______________________|_____________within this `[closure@src\main.rs:10:36: 10:43]`
| | |
| | required by a bound introduced by this call
11 | | let mut num = counter.lock().unwrap();
12 | | *num += 1;
13 | | });
| |_________^ `Rc<Mutex<i32>>` cannot be sent between threads safely
|
= help: within `[closure@src\main.rs:10:36: 10:43]`, the trait `Send` is not implemented for `Rc<Mutex<i32>>`
```
关键信息:<u>`Rc<Mutex<i32>>` cannot be sent between threads safely</u> 即`Rc<Mutex<i32>>`不能安全地在线程之间发送。`Rc<Mutex<i32>>`没有实现`Send` trait。
### 原子引用计数 `Arc`
`Arc<T>`是一种类似于`Rc<T>`的类型,可以在并发情况下安全使用。其中的`Arc`代表`原子引用计数(Atomic Reference Counting)`。有关原子性可查看`std::sync::atomic`文档。原子引用计数类型的工作方式类似于普通类型,但它可以安全地在线程之间共享。线程安全性会带来性能损失。
```rust
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}
// Result: 10
```
### `RefCell`/`Rc` 与 `Mutex`/`Arc` 的相似性
当我们使用 `Mutex<T>` 时,我们可以通过获取锁来获得对被保护数据的可变引用,即使 `Mutex<T>` 本身是不可变的。这就是 `Mutex<T>` 提供的 `内部可变性` 特性,类似于 `Cell` 系列一样。正如第十五章中使用 `RefCell` 可以改变 `Rc` 中的内容那样,同样的可以使用 `Mutex` 来改变 `Arc` 中的内容。
需要注意的是 Rust 并不能避免所有可能的逻辑错误。就像在第15章使用 `Rc<T>` 时可能出现引用循环(两个 `Rc<T>` 值相互引用导致内存泄漏)一样,使用 `Mutex<T>` 也可能导致死锁。死锁发生在两个或多个线程互相等待对方所拥有的锁,导致它们无法继续执行。这是程序设计中常见的并发问题之一。
## 可扩展的并发:`Sync` 和 `Send` trait
Rust 语言本身的并发功能非常少。到目前为止,在本章中讨论的几乎所有并发特性都是标准库中的,而不是语言本身的一部分。然而,两个并发概念内置在语言中:`std::marker` 中的 `Sync``Send` 标记trait。
标记 trait(Marker Trait)是一种特殊的 trait,它不包含方法定义,主要用于为类型附加特定的意义或属性,通过静态检查或约束保证类型的安全和正确性。而普通 trait 则定义了具体的行为,通过实现具体的方法来描述类型的行为和功能。
### 通过 `Send` 允许线程之间转移所有权
`Send` 标记trait 表明类型的所有权可以在线程间传递。几乎所有的 Rust 类型都是 `Send` 的。任何完全由 `Send` 类型组成的类型也会自动被标记为 `Send`。几乎所有的基本类型都是 `Send`,除了裸指针。
`Rc<T>` 不是 `Send` 类型,因为`Rc<T>` 被实现为用于单线程场景,这时不需要为拥有线程安全的引用计数而付出性能代价。在多线程下转移 `Rc<T>` 所有权会得到:`the trait Send is not implemented for Rc<Mutex<i32>>`。`Arc<T>`是 `Send` 的,故可以在并发情况下安全使用。
### `Sync` 允许多线程访问
`Sync` 标记trait 表明一个实现了 `Sync` 的类型可以安全的在多个线程中拥有其值的引用。换句话说,任意类型 `T` 如果 `&T`(`T` 的不可变引用)是 `Send` 的,那么 `T` 就是 `Sync` 的,这意味着该引用可以安全地发送到另一个线程。与 `Send` 类似,原始类型是 `Sync` 的,完全由 `Sync` 类型组成的类型也是 `Sync`
智能指针 `Rc<T>`、`RefCell<T>` 和 `Cell<T>` 不满足 `Sync` 特性的要求,因为它们的内部状态更新不是线程安全的。而 `Mutex<T>``Sync` 的,可以在多个线程中安全地共享访问。在多线程编程中,`Sync` 特性确保了类型可以在多个线程之间安全共享访问,避免了数据竞争和并发问题。
### 手动实现 Send 和 Sync 是不安全的
`Send``Sync` 特性组成的类型也是 `Send``Sync`的,故不必手动实现这些特性。因为他们是标记 trait,甚至都不需要实现任何方法。他们只是用来加强并发相关的不可变性的。**创建新的不由 `Send``Sync` 组成的新的并发类型需要多加小心,以确保维持其安全保证。**

@ -0,0 +1,730 @@
## Rust 的面向对象编程特性
面向对象编程(Object-Oriented Programming,OOP)是一种模式化编程方式。
## 面向对象语言的特点
Rust 受到许多编程范式的影响,例如:函数式编程的特性,面向对象编程(OOP)。面向对象编程语言共享某些共同特性,即对象、封装和继承。
### 对象:数据和行为的集合体
《设计模式:可复用面向对象软件的基础》中面向对象编程的定义:Object-oriented programs are made up of objects. An object packages both data and the procedures that operate on that data. The procedures are typically called methods or operations. 面向对象的程序是由对象组成的。一个 **对象** 包含数据和操作这些数据的过程。这些过程通常被称为 **方法****操作**
在这个定义下,Rust 是面向对象的:结构体和枚举包含数据,而 **impl** 块提供了对结构体和枚举的方法。虽然带有方法的结构体和枚举并不被称为 **对象**,但是他们提供了与对象相同的功能。
### 封装
封装(Encapsulation):对象的实现细节对于使用该对象的代码是不可访问的。因此,与对象交互的唯一方式是通过其公共接口(public API);使用对象的代码不应直接访问对象的内部并直接更改数据或行为。封装使得改变和重构对象的内部时无需改变使用对象的代码。
rust中可以使用`pub`关键字来决定哪些模块、类型、函数和方法是公共的,而默认情况下其他一切都是私有的。下面定义一个名为AveragedCollection的结构体:
```rust
pub struct AveragedCollection {
list: Vec<i32>,
average: f64,
}
```
结构体标记为`pub`,故其他代码可以使用它,但结构体内部的字段为私有。
```rust
impl AveragedCollection {
pub fn add(&mut self, value: i32){
self.list.push(value);
self.update_average();
}
pub fn remove(&mut self) -> Option<i32>{
let result = self.list.pop();
match result {
Some(value) => {
self.update_average();
Some(value)
}
None => None,
}
}
pub fn average(&self) -> f64{
self.average
}
fn update_average(&mut self){
let total:i32 = self.list.iter().sum();
self.average = total as f64 / self.list.len() as f64;
}
}
```
公共方法 `add`、`remove` 和 `average` 是访问或修改 `AveragedCollection` 实例中数据的唯一方式。当使用 `add` 方法向列表添加项目或使用 `remove` 方法删除项目时,每个方法的实现都会调用私有的 `update_average` 方法,该方法负责更新平均值字段。`pub` 或非 `pub` 选项的使用使得可以封装实现细节。
![image-20230808172113729](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230808172113729.png)
### 继承
在面向对象编程中,继承有两个主要用途:
1. **代码重用:** 通过继承,您可以在一个类中实现一些特定的功能,然后在另一个类中继承这些功能。这意味着您可以避免重复编写相同的代码,提高代码的可维护性和复用性。
2. **类型系统和多态性:** 继承也可以用于实现多态性,即子类可以被视为其父类的一种类型,从而可以在某些情况下替代父类使用。这种特性使得代码更灵活,可以根据不同的需求使用不同的子类对象,而不需要改变大量的代码。
然而,在 Rust 中,传统的继承机制并不像其他一些编程语言(如Java或C++)那样直接。Rust 鼓励使用`组合`和 `trait` 来实现类似的功能,以更安全和灵活的方式处理代码重用和类型多态性。
总之,尽管 Rust 中的继承不同于其他语言中的继承,但它提供了其他的方法来实现类似的概念,使得代码可以更加可靠地工作并且更易于维护。
> 多态性是一种编程概念,它允许代码在处理不同类型数据时具有灵活性,而在 Rust 中通过泛型和 trait 实现了更安全和更灵活的多态性。
>
> 在传统的面向对象编程中,多态性通常与继承概念相关联,即基类和子类之间可以具有相同的方法名,但实际行为可能不同。然而,在 Rust 中,多态性的实现方式略有不同。Rust 使用泛型(Generics)来实现参数化类型,允许代码适用于多种类型而不关心具体类型是什么。同时,Rust 使用 trait(特征)来定义一组共享的方法或功能,通过 trait 约束可以确保泛型类型必须提供特定的方法。这种多态性被称为有界参数多态性(Bounded Parametric Polymorphism),它在泛型和 trait 的约束下实现。
## 为使用不同类型的值而设计的 trait 对象
向量只能存储相同类型的值。但一个枚举的变体是在同一个枚举类型下定义的,故需要一个类型来表示不同类型的元素时,可以定义一个`枚举`来实现在每个单元格中存储不同类型的数据,并仍能拥有一个代表一排单元的 `vector`
若希望允许库的用户在特定情况下扩展有效的数据类型集合。传统的`继承`在这种情况下可能不适用,因为 Rust 不支持继承。但我们可以使用 `trait` 对象让用户使用不同的类型进行扩展。 `trait` 对象允许不同类型的值在运行时表现出多态性,从而在不同类型之间实现共享的绘制方法。这样,用户可以灵活地扩展库,而无需使用继承。
### 定义通用行为的 trait (基类/接口)
为了实现特定的行为,下面定义了一个名为 `Draw``trait`,其中包含一个 `draw` 方法。接着可以定义一个存放 `trait` 对象(trait object) 的 vector。`trait` 对象指向实现了指定 `trait` 的类型的实例,允许在运行时查找 `trait` 方法。创建 `trait` 对象需要指定某种类型的指针(例如 `&` 引用或 `Box<T>` 智能指针),使用 `dyn` 关键字,并指定相关的 `trait`。`trait` 对象可以在泛型或具体类型的位置使用,Rust 的类型系统会在编译时验证所使用的值是否实现了 `trait` 对象的 `trait`。我们可以使用 `trait` 对象来实现多态性。
在 Rust 中,结构体和枚举不被称为 `对象`,而是将数据和行为分开处理。`Trait` 对象在某种程度上类似于其他语言中的对象,但不同之处在于它们无法添加数据,专注于允许通用行为的抽象。
```rust
pub trait Draw {
fn draw(&self);
}
```
存放 trait 对象的 vector:
```rust
pub struct Screen {
pub components: Vec<Box<dyn Draw>>,
// vector 的类型是 Box<Draw>,此为一个 trait 对象:它是 Box 中任何实现了 Draw trait 的类型的替身。
}
impl Screen {
pub fn run(&self){
for component in self.components.iter(){
component.draw();
}
}
}
```
![image-20230810160017233](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230810160017233.png)
泛型类型参数一次只能替代一个具体类型,而 trait 对象则允许在运行时替代多种具体类型。添加泛型后 `Screen` 实例必须存放同一类型。
```rust
pub struct Screen<T: Draw> {
pub components: Vec<T>,
}
impl<T> Screen<T> where T: Draw,{
pub fn run(&self) {
for component in self.components.iter() {
component.draw();
}
}
}
```
![image-20230810161147055](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230810161147055.png)
### 实现 trait
按照书中案例,添加结构体实现 `Draw` trait。使用 trait 对象来存储实现了相同 trait 的不同类型的值。
```rust
pub trait Draw {
fn draw(&self);
}
pub struct Screen {
pub components: Vec<Box<dyn Draw>>,
}
impl Screen {
pub fn run(&self) {
for component in self.components.iter() {
component.draw();
}
}
}
pub struct Button {
pub width: u32,
pub height: u32,
pub label: String,
}
impl Draw for Button {
fn draw(&self) {
// 实际绘制按钮的代码
}
}
impl Button {
fn hello(&self){ // 添加额外方法。
println!("hello");
}
}
// SelectBox 结构体
struct SelectBox {
width: u32,
height: u32,
options: Vec<String>,
}
impl Draw for SelectBox {
fn draw(&self) {
// code to actually draw a select box
}
}
```
![image-20230810163847955](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230810163847955.png)
在编写库时,我们并不知道可能会添加 `SelectBox` 类型,但是我们的 `Screen` 实现能够处理这种新类型并进行绘制,因为 `SelectBox` 实现了 `Draw` trait,这意味着它实现了 `draw` 方法。
这个概念强调关注一个值反映的消息,而不关心值的具体类型,类似于动态类型语言中的鸭子类型:如果它走起来像鸭子,叫起来像鸭子,那么它就是鸭子!在 `Screen``run` 实现中,我们不需要知道每个组件的具体类型。它不会检查组件是 `Button` 还是 `SelectBox` 的实例,只会调用组件的 `draw` 方法。通过将 `Box<dyn Draw>` 指定为 `components` 向量中值的类型,我们定义了 `Screen` 需要值实现能够调用 `draw` 方法的特性。
使用 trait 对象和 Rust 的类型系统编写类似鸭子类型的操作的优势在于,我们永远不必在运行时检查一个值是否实现了特定的方法,也不必担心如果一个值没有实现某个方法但我们仍然调用它会出错。如果值没有实现 trait 对象需要的特性,Rust 将不会编译我们的代码。
![image-20230810170824925](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230810170824925.png)
实现Draw trait 后:
![image-20230810170900250](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230810170900250.png)
### Trait对象执行动态分发
编译器在泛型上使用 trait bound 时所进行单态化处理:编译器为每一个被泛型类型参数代替的具体类型生成了非泛型的函数和方法实现。单态化所产生的代码进行 `静态分发(static dispatch)`。静态分发发生于编译器在`编译时`就知晓调用了什么方法的时候。这与 `动态分发 (dynamic dispatch)`相反,这时编译器在编译时无法知晓调用了什么方法。在动态分发的情况下,编译器会生成在`运行时`确定调用了什么方法的代码。
当使用 `trait` 对象时,Rust必须使用`动态分发`。编译器无法知晓所有可能用于 `trait` 对象代码的类型,所以它也不知道应该调用哪个类型的哪个方法实现。为此在运行时,Rust使用 `trait` 对象中的指针来知晓需要调用哪个方法。这种查找会产生运行时的开销,而静态分发则不会发生这种情况。动态分发还阻止编译器选择内联方法的代码,从而阻止了某些优化。
### Trait 对象要求对象安全
只有 `对象安全(object safe` 的 trait 才可以组成 trait 对象。围绕所有使得 trait 对象安全的属性存在一些复杂的规则,不过在实践中,只涉及到两条规则。如果一个 trait 中所有的方法有如下属性时,则该 trait 是对象安全的:
1. 返回值类型不为 Self
2. 方法没有任何泛型类型参数
`Clone` trait: `clone` 方法的返回类型是 `Self`,所以不能将 `Clone` trait 用于构建安全的 trait 对象。
```rust
pub trait Clone: Sized {
// Required method
fn clone(&self) -> Self;
// Provided method
fn clone_from(&mut self, source: &Self) { ... }
}
```
使用clone 替换 Draw 是会产生错误。
![image-20230810174753449](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230810174753449.png)
对象安全性涉及到在 trait 中使用 `Self` 关键字和泛型类型参数的情况。
1. 使用 `Self` 关键字:`Self` 是一个类型别名,表示要实现该 trait 或方法的类型。当一个 trait 方法的返回类型是 `Self` 时,这意味着方法返回了实现该 trait 的具体类型。然而,在使用 trait 对象时,无法知道实现该 trait 的具体类型是什么,因为 trait 对象抹去了具体类型的信息。如果一个 trait 方法返回的是具体的 `Self` 类型,但 trait 对象却忘记了实际的类型,那么就无法使用已经忘却的具体类型。因此,为了确保在 trait 对象中能够正常使用方法,这个方法的返回类型不能是 `Self`
2. 使用泛型类型参数:在使用泛型类型参数时,将具体的类型参数放入 trait 中,这个具体类型变成了实现该 trait 的类型的一部分。然而,在使用 trait 对象时,具体的类型参数信息被擦除,无法知道放入泛型参数的具体类型是什么。
## 实现面向对象设计模式
状态模式(*state pattern*)是一种面向对象的设计模式。模式的关键在于一个值有某些内部状态,体现为一系列的 **状态对象**,同时值的行为随着其内部状态而改变。
使用状态模式的优势在于,当程序的业务需求发生变化时,我们无需更改保存状态的值的代码或使用该值的代码。我们只需要更新一个状态对象内部的代码,以更改其规则或者可能添加更多的状态对象。
下面通过状态模式来开发博客功能:
### 定义 `Post` 并新建一个草案状态的实例
```rust
trait State {}
struct Draft {}
impl State for Draft{}
pub struct Post {
state: Option<Box<dyn State>>,
content: String,
}
impl Post {
pub fn new() -> Post{
Post {
state: Some(Box::new(Draft {})),
content: String::new(),
}
}
}
```
State trait 定义了所有不同状态的博文所共享的行为,同时 Draft、PendingReview 和 Published 状态都会实现 State 状态。现在这个 trait 并没有任何方法,同时开始将只定义 Draft 状态因为这是我们希望博文的初始状态。
### 存放博文内容的文本
通过实现为add_text方法,而不是将content字段公开为pub,以便以后我们可以实现一个控制如何读取content字段数据的方法。
![image-20230828091049555](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230828091049555.png)
### 请求审核博文来改变其状态
![image-20230828101609290](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230828101609290.png)
这段代码的作用是,它首先尝试从`self.state`中取出当前状态值。如果存在状态值,就调用当前状态的`request_review()`方法,获得请求审查后的新状态,并将其设置为`self.state`,这样文章的状态就会从草稿状态转变为待审查状态。如果`self.state`中没有状态值(即为`None`),则什么也不做,因为不可能在没有状态的情况下进行状态转换。
```rust
trait State {
fn request_review(self: Box<Self>) -> Box<dyn State>;
}
struct Draft {}
impl State for Draft{
fn request_review(self: Box<Self>) -> Box<dyn State> {
Box::new(PendingReview{})
}
}
struct PendingReview {}
impl State for PendingReview {
fn request_review(self: Box<Self>) -> Box<dyn State>{
self
}
}
pub struct Post {
state: Option<Box<dyn State>>,
content: String,
}
impl Post {
pub fn new() -> Post{
Post {
state: Some(Box::new(Draft {})),
content: String::new(),
}
}
pub fn add_text(&mut self, text: &str){
self.content.push_str(text);
}
pub fn request_review(&mut self){
if let Some(s) = self.state.take(){
self.state = Some(s.request_review())
}
}
}
```
将`request_review`方法添加到`State` trait中;现在所有实现该trait的类型都需要实现`request_review`方法。
请注意,方法的第一个参数不再是`self`、`&self`或`&mut self`,而是`self: Box<Self>`。这种语法意味着该方法只在调用类型为`Box`的变量上时有效。这种语法会获取`Box<Self>`的所有权,使旧状态无效,从而让`Post`的状态值转变为新状态。
为了消耗旧状态,`request_review`方法需要获取状态值的所有权。这就是`Post`的`state`字段中的`Option`发挥作用的地方:我们调用`take`方法,将`state`字段中的`Some`值取出,并在其位置留下`None`,因为Rust不允许在结构体中存在未填充的字段。这使我们可以将状态值从`Post`中移出,而不是借用它。然后,我们将文章的状态值设置为此操作的结果。
我们需要临时将`state`设置为`None`,而不是直接使用诸如`self.state = self.state.request_review();`之类的代码来获取状态值的所有权。这确保了在我们将其转换为新状态后,`Post`不能再使用旧状态值。
现在,我们可以开始看到状态模式的优势:不管`Post`的状态值如何,`request_review`方法都是相同的。每个状态都对其自身的规则负责。
![image-20230828105335152](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230828105335152.png)
### 增加改变 `content``approve` 方法
`approve` 方法将类似于 `request_review` 方法:当状态被批准时,它将把状态设置为当前状态所指定的值
```rust
trait State {
fn request_review(self: Box<Self>) -> Box<dyn State>;
fn approve(self: Box<Self>) -> Box<dyn State>;
}
struct Draft {}
impl State for Draft{
// --snip--
fn approve(self: Box<Self>) -> Box<dyn State>{
self
}
}
struct PendingReview {}
impl State for PendingReview {
// --snip--
fn approve(self: Box<Self>) -> Box<dyn State> {
Box::new(Published{})
}
}
struct Published {}
impl State for Published {
fn request_review(self: Box<Self>) -> Box<dyn State> {
self
}
fn approve(self: Box<Self>) -> Box<dyn State> {
self
}
}
pub struct Post {
state: Option<Box<dyn State>>,
content: String,
}
impl Post {
// --snip--
pub fn approve(&mut self){
if let Some(s) = self.state.take(){
self.state = Some(s.approve())
}
}
pub fn content(&self) -> &str{
""
}
}
```
我们将 `approve` 方法添加到 `State` trait 中,并添加一个新的结构体来实现 `State`,即 `Published` 状态。
类似于对 `PendingReview` 上的 `request_review` 方法的处理方式,如果我们在 `Draft` 上调用 `approve` 方法,它将没有任何效果,因为 `approve` 将返回 `self`。当我们在 `PendingReview` 上调用 `approve` 时,它将返回一个新的、装箱的 `Published` 结构体的实例。`Published` 结构体实现了 `State` trait,在 `request_review` 方法和 `approve` 方法上,它都返回自身,因为在这些情况下,文章应保持在 `Published` 状态。
现在,我们需要更新 `Post` 上的 `content` 方法。我们希望从 `content` 返回的值取决于 `Post` 的当前状态,因此我们将让 `Post` 委托给其状态上定义的 `content` 方法,如第17-17节所示:
```rust
impl Post {
// --snip--
pub fn content(&self) -> &str {
self.state.as_ref().unwrap().content(self)
}
// --snip--
}
```
`Option` 上调用 `as_ref` 方法,获得一个对 `Option` 内部值的引用,而不是值的所有权。因为 state 是一个 `Option<Box<dyn State>>`,当我们调用 as_ref 时,会返回一个 `Option<&Box<dyn State>>`。如果我们没有调用 as_ref,就会出错,因为我们不能从函数参数的借用 &self 中移动 state。
然后,我们调用 `unwrap` 方法,我们知道这绝对不会导致 panic,因为我们知道 `Post` 上的方法会确保在这些方法完成时,`state` 会始终包含一个 `Some` 值。
在这个时候,当我们在 `&Box<dyn State>` 上调用 `content` 时,解引用强制转换(Deref Coercion)会生效,使得 `content` 方法最终在实现 `State` trait 的类型上被调用。因此,我们需要将 `content` 添加到 `State` trait 的定义中,这就是根据不同的状态确定返回什么内容的逻辑所在,
```
trait State {
fn request_review(self: Box<Self>) -> Box<dyn State>;
fn approve(self: Box<Self>) -> Box<dyn State>;
fn content<'a>(&self, post: &'a Post) -> &'a str{""}
}
struct Draft {}
impl State for Draft{
fn request_review(self: Box<Self>) -> Box<dyn State> {
Box::new(PendingReview{})
}
fn approve(self: Box<Self>) -> Box<dyn State>{
self
}
}
struct PendingReview {}
impl State for PendingReview {
fn request_review(self: Box<Self>) -> Box<dyn State>{
self
}
fn approve(self: Box<Self>) -> Box<dyn State> {
Box::new(Published{})
}
}
struct Published {}
impl State for Published {
fn request_review(self: Box<Self>) -> Box<dyn State> {
self
}
fn approve(self: Box<Self>) -> Box<dyn State> {
self
}
fn content<'a>(&self, post: &'a Post) -> &'a str {
&post.content
}
}
pub struct Post {
state: Option<Box<dyn State>>,
content: String,
}
impl Post {
pub fn new() -> Post{
Post {
state: Some(Box::new(Draft {})),
content: String::new(),
}
}
pub fn add_text(&mut self, text: &str){
self.content.push_str(text);
}
pub fn request_review(&mut self){
if let Some(s) = self.state.take(){
self.state = Some(s.request_review())
}
}
pub fn approve(&mut self){
if let Some(s) = self.state.take(){
self.state = Some(s.approve())
}
}
pub fn content(&self) -> &str{
self.state.as_ref().unwrap().content(&self)
}
}
```
![image-20230828150456750](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230828150456750.png)
至此已实现了状态模式,与规则相关的逻辑存放在状态对象中,而不是分散在 `Post` 中。运行图:
![image-20230828151711389](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230828151711389.png)
枚举也可以实现此功能,将不同的文章状态作为变体。使用枚举的一个缺点是,每个检查枚举值的地方都需要一个 `match` 表达式或类似的结构来处理每个可能的变体。与这种 `trait` 对象的解决方案相比,会产生更多重复。
### 状态模式的权衡之处
优点:
- **清晰可读:** 状态模式能够消除大量的条件逻辑,使代码更加清晰和易读。
- **扩展性:** 添加新状态和行为相对容易,只需要创建新的结构体并实现相应的trait方法。
- **单一职责:** 每个状态实现只关注自己的行为,提高了代码的模块化和可维护性。
**弊端:**
- **状态耦合:** 状态之间的转换可能会导致一些状态耦合,特定的状态转换会影响到其他状态的实现。
- **部分重复逻辑:** 一些相似的方法实现可能会出现重复,例如在Post对象的不同方法中委托相同的操作。
- **trait安全性:** 在trait对象中,无法为trait方法提供默认实现返回`self`,这可能导致一些逻辑重复。
修改代码逻辑:
1. 只允许博文处于 Draft 状态时增加文本内容。
2. 增加 reject 方法将博文的状态从 PendingReview 变回 Draft。
3. 在将状态变为 Published 之前需要两次 approve 调用。
```rust
trait State {
// --snip--
fn reject(self: Box<Self>) -> Box<dyn State>;
fn can_add_text(&self) -> bool {
false
}
}
struct Published {}
impl State for Published {
// --snip--
}
struct Draft {}
impl State for Draft{
// --snip--
fn can_add_text(&self) -> bool {
true
}
}
struct PendingReview {
approve_count: u32,
}
impl State for PendingReview {
fn request_review(self: Box<Self>) -> Box<dyn State>{
self
}
fn approve(self: Box<Self>) -> Box<dyn State> {
if self.approve_count < 1{
Box::new(PendingReview {
approve_count: self.approve_count+1,
})
}else{
Box::new(Published{})
}
}
fn reject(self: Box<Self>) -> Box<dyn State> {
Box::new(Draft{})
}
}
pub struct Post {
// --snip--
}
impl Post {
// --snip--
pub fn add_text(&mut self, text: &str) {
if let Some(state) = self.state.as_mut() {
if state.can_add_text() {
self.content.push_str(text);
}
}
}
pub fn reject(&mut self){
if let Some(s) = self.state.take(){
self.state = Some(s.reject())
}
}
// --snip--
}
```
![image-20230828164143599](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230828164143599.png)
#### 将状态和行为编码为类型
使用不同的类型来编码不同的状态,从而利用Rust的`类型检查系统`来防止在不允许的情况下使用错误的状态。通过使用类型系统,可以在编译时捕获错误,从而提供更强的安全性和可靠性。
```rust
pub struct Post {
content: String,
}
impl Post {
pub fn new() -> DraftPost{
DraftPost {
content: String::new(),
}
}
pub fn content(&self) -> &str{
&self.content
}
}
pub struct DraftPost {
content: String,
}
impl DraftPost {
pub fn add_text(&mut self, text: &str) {
self.content.push_str(text);
}
}
```
Post 和 DraftPost 结构体都有一个私有的 content 字段来储存博文的文本。这些结构体不再有 state 字段因为我们将类型编码为结构体的类型。Post 将代表发布的博文,它有一个返回 content 的 content 方法。
Post::new 函数,不过不同于返回 Post 实例,它返回 DraftPost 的实例。现在不可能创建一个 Post 实例,因为 content 是`私有`的同时没有任何函数返回 Post。
DraftPost 上定义了一个 add_text 方法,这样就可以像之前那样向 content 增加文本,不过注意 DraftPost 并没有定义 content 方法!如此现在程序`确保了所有博文都从草案开始`,同时草案博文没有任何可供展示的内容。任何绕过这些限制的尝试都会产生编译错误。
#### 实现状态转移为不同类型的转换
```rust
pub struct Post {
content: String,
}
impl Post {
pub fn new() -> DraftPost{
DraftPost {
content: String::new(),
}
}
pub fn content(&self) -> &str{
&self.content
}
}
pub struct DraftPost {
content: String,
}
impl DraftPost {
pub fn add_text(&mut self, text: &str) {
self.content.push_str(text);
}
// --snip--
pub fn request_review(self) -> PendingReviewPost { // self 获取所有权
PendingReviewPost {
content: self.content,
}
}
}
pub struct PendingReviewPost {
content: String,
}
impl PendingReviewPost {
pub fn approve(self) -> Post{ // self 获取所有权
Post {
content: self.content,
}
}
}
// main 函数
use blog_type::Post;
fn main() {
let mut post = Post::new();
post.add_text("yes or no");
post.add_text(", yes");
let post = post.request_review();
// post.add_text("yes or no");
let post = post.approve();
println!("content: {0}", post.content());
}
```
`request_review``approve ` 方法获取 self 的所有权,因此会消费 `DraftPost``PendingReviewPost` 实例,并分别转换为 `PendingReviewPost` 和发布的 `Post`。`request_review` 和 `approve` 方法返回新的实例,而不是修改调用它们的结构体,因此我们需要添加更多的 `let post` 重新赋值语句来保存返回的实例。
![image-20230829105403832](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230829105403832.png)
面向对象模式在 Rust 中并不总是最佳解决方案。这种作法使用了Rust的类型系统和所有权机制来实现状态模式,相比传统的面向对象方法,有一些区别:
1. **Ownership and Borrowing**: 在这种方法中,状态转换时使用了所有权的转移,例如从 `DraftPost` 转换到 `PendingReviewPost` 或从 `PendingReviewPost` 转换到 `Post`。这允许每个状态在转换时拥有 `content` 字段的所有权,从而防止在不正确的状态下操作。这是通过获取和释放所有权来确保状态的合理转换。
2. **Compile-Time Checks**: 使用不同的类型来表示不同的状态,例如 `DraftPost`、`PendingReviewPost` 和 `Post`,可以在编译时进行静态类型检查,防止在错误的状态下执行操作。这种方法可以在编译阶段捕获可能的错误,而不是在运行时出现问题。
3. **No Trait Objects**: 这种方法不涉及动态分发或使用 trait 对象,因此不需要使用 trait 中的方法。相反,不同的状态由不同的类型来表示,并且每个类型都有自己的方法。这可以提高代码的可读性和性能。
4. **Explicit State Transitions**: 状态之间的转换是显式的,通过调用各个状态的方法来实现。这使得代码更易理解和调试。
综上所述,这种方法利用了Rust强大的类型系统和所有权机制,以一种更安全、更明确的方式来实现状态模式,同时避免了一些可能的运行时错误。这种方式与传统的面向对象方法相比,在一些方面可能会更加清晰和可靠。

@ -0,0 +1,889 @@
## 模式和匹配
模式是一种特殊的语法,用于匹配不同类型的结构,可以是简单的也可以是复杂的。通过将模式与匹配表达式等结构相结合,可以更好地控制程序的控制流。模式可以由以下部分组成:
- 字面量:匹配特定的常量值。
- 解构的数组、枚举、结构或元组:从中提取值以进行匹配。
- 变量:将匹配的部分绑定到变量,以便在代码中使用。
- 通配符:用于匹配任意值,不关心具体内容。
- 占位符:类似于通配符,但可以将匹配的部分绑定到特定变量。
## 所有可能会用到模式的位置
### match分支
在形式上 `match` 表达式由 `match` 关键字、用于匹配的值和一个或多个分支构成,这些分支包含一个模式和在值匹配分支的模式时运行的表达式:
```rust
match VALUE {
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
}
```
例如 Option<i32> 值的 match 表达式:
```rust
match x {
None => None,
Some(i) => Some(i + 1),
}
```
match 表达式的一个要求是,它们必须是`穷尽`的,也就是说,match 表达式中值的所有可能情况都必须考虑到。一个确保覆盖每个可能值的方法是在最后一个分支使用捕获`所有`的模式。特定的 `_` 模式将匹配任何内容,但它永远不会绑定到变量,因此通常在最后一个匹配分支中使用。例如,当您希望忽略未指定的任何值时,`_` 模式可以很有用。
### if let条件表达式
if let 表达式,主要用于编写等同于只关心一个情况的 `match` 语句简写的。`if let` 可以对应一个可选的带有代码的 `else``if let` 中的模式不匹配时运行。
```rust
fn main() {
let favorite_color: Option<&str> = None;
let is_tuesday = false;
let age: Result<u8, _> = "34".parse();
if let Some(color) = favorite_color {
println!("Using your favorite color, {}, as the background", color);
} else if is_tuesday {
println!("Tuesday is green day!");
} else if let Ok(age) = age { // 引入与 match 分支相同的遮蔽变量
if age > 30{
println!("{} Using purple as the background color", age);
} else{
println!("Using blue as the background color");
}
}
}
```
![image-20230830105332596](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230830105332596.png)
if let 也可以引入与 match 分支相同的遮蔽变量:`if let Ok(age) = age` 这一行引入了一个新的遮蔽的 age 变量,其中包含了 Ok 变体中的值。
**`覆盖`变量**是指在一个作用域内,一个变量的名字被重新引入并`覆盖`外部作用域中同名的变量。这意味着在内部作用域中,通过相同的变量名,你可以访问内部作用域中的变量,而外部作用域中的变量将被`覆盖`,暂时无法直接访问。
使用 `if let` 表达式的缺点是,编译器不会检查`穷尽性`,而 `match` 表达式会进行检查。如果省略了最后的 `else` 块,从而未处理某些情况,编译器将不会提示可能的逻辑错误。
### while let 条件循环
只要模式匹配就一直进行 `while` 循环。
```rust
rustfn main() {
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
while let Some(top) = stack.pop() {
println!("{}", top);
}
}
```
![image-20230830105535658](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230830105535658.png)
### for循环
`for` 循环是 Rust 中最常见的循环结构。
```
fn main() {
let v = vec!['a', 'b', 'c'];
for (index, value) in v.iter().enumerate(){
println!("{} is at index {}", value, index);
}
}
```
![image-20230830112314654](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230830112314654.png)
`enumerate()` 方法需要在一个迭代器上调用,因此需要首先使用 `iter()` 方法将对象转换为一个迭代器。`enumerate()` 方法会返回一个迭代器,该迭代器会生成元组,包括索引和原始值。
### let语句
在本章之前,我们只明确讨论了在 `match``if let` 中使用模式,但实际上,我们还在其他地方使用了模式,包括在 `let` 语句中。
```rust
let x = 5;
```
`let` 语句更正式的写法:
```rust
let PATTERN = EXPRESSION;
```
像 let x = 5; 这样的语句中,PATTERN 位置上的变量名实际上是模式的一种简单的形式。Rust 会将表达式与模式进行比较,并赋予它找到的任何名称。因此,在 let x = 5; 的示例中,x 是一个模式,表示`将匹配到的值绑定到变量 x`。因为变量名 x 是整个模式,这个模式实际上意味着`将任何值都绑定到变量 x,无论值是什么`。
`let` 和模式解构一个元组:
```rust
let (x, y, z) = (1, 2, 3);
```
这里将一个元组与模式匹配。Rust 会比较值 `(1, 2, 3)` 与模式 `(x, y, z)` 并发现此值匹配这个模式。可以将这个元组模式看作是将三个独立的变量模式结合在一起。
当模式中元素数量与元祖元素不匹配时:
1. 当元素值多于模式值:
1. 多出一个时:`_` 通常用作通配符,表示忽略掉对应位置的值。
```rust
let (x, _, y) = (1, 2, 3);
// x = 1, y = 3
```
2. 多出大于一个时:`..` 用于表示忽略某个范围内的元素。
```rust
let (x, y, ..) = (1, 2, 3, 4, 5);
```
2. 当元素值少于模式值:去除模式中多出的变量.
### 函数参数
函数参数也可以是模式。
```rust
fn foo(x: i32) {
// 代码在这里
}
```
这里的 `x` 就是一个模式,故可以像在let时所做的一样,我们也可以在函数的参数中匹配一个元组到模式:
```rust
fn print_coordinates(&(x, y): &(i32, i32)) {
println!("Current location: ({}, {})", x, y);
}
fn main() {
let point = (3, 5);
print_coordinates(&point);
}
```
![image-20230830150622308](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230830150622308.png)
因为闭包与函数相似,故可以在闭包参数列表中以与函数参数列表相同的方式使用模式。
## Refutability:何时模式可能会无法匹配
模式有两种形式:`可反驳(refutable)`和`不可反驳(irrefutable)`。对于任何可能传递的值都会匹配的模式是`不可反驳的`。例如:let x = 5; 这个模式中的 `x` 可以匹配任何值,因此不会失败。
当模式可能无法与某些可能的值匹配时,这个模式被称为`可反驳的`模式。一个示例是表达式 if let Some(x) = a_value 中的 Some(x),因为如果 a_value 变量中的值是 None 而不是 Some,Some(x) 模式将无法匹配。
让我们看一个示例,当我们尝试在 Rust 要求不可反驳模式的地方使用可反驳模式
```rust
let value: Option<i32> = Some(1);
// let value: Option<i32> = None;
let Some(s) = value; // ^^^^^^^ pattern `None` not covered
```
如果 some_option_value 是 None 值,它将无法匹配模式 Some(x),这意味着模式是可反驳的。然而,let 语句只能接受不可反驳的模式。
```
error[E0005]: refutable pattern in local binding
--> src\main.rs:3:9
|
3 | let Some(s) = value;
| ^^^^^^^ pattern `None` not covered
|
= note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
= note: for more information, visit https://doc.rust-lang.org/book/ch18-02-refutability.html
= note: the matched value is of type `Option<i32>`
help: you might want to use `let else` to handle the variant that isn't matched
|
3 | let Some(s) = value else { todo!() };
| ++++++++++++++++
error[E0005]: refutable pattern in local binding
```
修改代码通过 `if let` 解决此问题:
```rust
fn main() {
let value: Option<i32> = Some(1);
if let Some(x) = value {
println!("x: {0}", x);
}
let value: Option<i32> = None;
if let Some(x) = value {
println!("x: {0}", x);
}
}
```
![image-20230830153445776](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230830153445776.png)
`if let` 提供了一个总是会匹配的模式:
```
if let x = 5 {
println!("{}", x);
};
```
代码可运行,但编译器会发出警告:
```
warning: irrefutable `if let` pattern
--> src\main.rs:11:8
|
11 | if let x = 5 {
| ^^^^^^^^^
|
= note: this pattern will always match, so the `if let` is useless
= help: consider replacing the `if let` with a `let`
= note: `#[warn(irrefutable_let_patterns)]` on by default
```
在 Rust 中,模式分为不可反驳模式和可反驳模式:
**不可反驳模式(Irrefutable Patterns):** 不可反驳的模式确保总是会成功匹配,并且总是有一个对应的值。
- 用于确定位置的情况,不会导致匹配失败。
- 例如:`变量绑定`、`函数参数`、`for` 循环中的迭代器解构等。
- 适用于所有可能的值,不会引起匹配失败。
**可反驳模式(Refutable Patterns):**可反驳模式是指在模式匹配时可能会失败的模式。
- 用于可能导致匹配失败的情况。
- 例如:`match` 表达式、`if let` 表达式、`while let` 表达式,以及解构复合类型(如元组、结构体、枚举)。
- 需要通过条件语句(`if let`、`while let`)或 `match` 表达式来处理可能的匹配失败情况。
需要注意的是,编写稳健的 Rust 代码需要正确处理模式匹配失败的情况,以确保程序的可靠性。不可反驳的模式用于确定位置,不会导致匹配失败,而可反驳的模式用于处理可能的匹配失败情况。
## 模式语法
### 匹配字面量
```rust
fn main() {
let x = 1;
match x {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
_ => println!("anything"),
}
}
// one
```
### 匹配变量名
命名变量是匹配任何值的不可反驳模式,match表达式可能会出现`变量覆盖(shadowing)`问题。
```rust
fn main() {
let x = Some(5);
let y = 10;
match x {
Some(50) => println!("Got 50"),
Some(y) => println!("Matched, y = {:?}", y),
_ => println!("Default case, x = {:?}", x),
}
println!("at the end: x = {:?}, y = {:?}", x, y);
}
// Matched, y = 5
// at the end: x = Some(5), y = 10
```
首先,代码会尝试匹配模式`Some(50)`,但是`x`的值是`Some(5)`,因此不匹配这个分支。接着,代码会尝试匹配模式`Some(y)`,在这个模式中,匹配会创建一个新的变量`y`,这个新的 `y` 绑定会匹配任何 `Some` 中的值。然后代码会进入这个分支,输出`Matched, y = 5`。最后,`_`分支是一个通配分支,如果前面的模式都不匹配(即 x 为None时),就会进入这个分支,输出`Default case, x = None`。
`match` 表达式完成时,它的作用域结束,内部的 `y` 作用域也随之结束。最后的 `println!` 语句会输出 `at the end: x = Some(5), y = 10`
### 多模式匹配
`match` 表达式中,可以使用 `|` 语法来匹配多个模式,这就是模式或运算符。
```rust
x = 1;
match x {
1 | 2 => println!("one or two"),
3 => println!("three"),
_ => println!("anything"),
}
// one or two
```
### ..= 匹配值的范围
只允许在 `数字``char` 上使用范围。
```rust
let x = 5;
match x {
1 ..= 5 => println!("one through five"),
_ => println!("something else"),
}
// one through five
```
在计算机内部,字符类型实际上是用数字表示的。在 ASCII 编码中,字母 `'a'` 对应的整数值是 97,字母 `'b'` 对应的整数值是 98,依此类推。
```rust
let x = 'c';
match x {
'a'..='j' => println!("early ASCII letter"),
'k'..='z' => println!("late ASCII letter"),
_ => println!("something else"),
}
// early ASCII letter
```
### 解构并分解值
使用模式来解构结构体、枚举、元组和引用,以使用这些值的不同部分。
#### 解构结构体
如果你只想要结构体中的一个字段,而不关心另一个字段,你可以使用下划线 `_` 来忽略那个不需要的字段。
```rust
struct Point {
x: i32,
y: i64,
}
fn main() {
let p = Point {x: 0, y: 7};
let Point {x: a, y:b } = p;
println!("a is {}, b is {}", a, b);
// assert_eq!(0, a); assert_eq!(7, b);
}
```
![image-20230831135203062](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230831135203062.png)
此代码创建了变量 `a``b`,它们匹配了 `p` 结构体的 `x``y` 字段的值。在这里变量名与结构体字段名不一致,可进行如下修改:
```rust
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point {x: 0, y: 7};
let Point {x,y } = p;
println!("x is {}, y is {}", x, y);
}
```
![image-20230831135943745](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230831135943745.png)
这段代码创建了与变量 `p``x``y` 字段匹配的变量 `x``y`。结果是变量 `x``y` 包含了来自结构体 `p` 的值。
下面使用 `match` 表达式,处理不同三种情况的Point值:
```rust
fn main() {
let p = Point { x: 0, y: 7 };
match p {
Point { x, y: 0 } => println!("On the x axis at {x}"),
Point { x: 0, y } => println!("On the y axis at {y}"),
Point { x, y } => {
println!("On neither axis: ({x}, {y})");
}
}
}
// On the y axis at 7
```
![image-20230831143943913](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230831143943913.png)
在 Rust 中,模式匹配可以创建变量,但这些变量的作用域通常只在匹配的代码块内。一旦离开该代码块,这些变量就会被销毁。
#### 解构枚举
下面以第六章的 `Message` 枚举为例,编写了一个 `match` 语句,使用相应的解构模式,对每个内部值进行解构:
```rust
enum Message {
Quit,
Move {x:i32, y:i32},
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let msg = Message::ChangeColor(0, 160, 225);
match msg {
Message::Quit => {
println!("The Quit variant has no data to destructure.");
}
Message::Move { x, y } => {
println!("Move in the x direction {x} and in the y direction {y}");
}
Message::Write(text) => {
println!("Text message: {text}");
}
Message::ChangeColor(r, g, b) => {
println!("Change the color to red {r}, green {g}, and blue {b}",)
}
}
}
// Change the color to red 0, green 160, and blue 225
```
#### 解构嵌套的结构体和枚举
```rust
enum Color {
Rgb(i32, i32, i32),
Hsv(i32, i32, i32),
}
enum Message {
Quit,
Move {x:i32, y:i32},
Write(String),
ChangeColor(Color),
}
fn main() {
let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));
match msg {
Message::ChangeColor(Color::Hsv(h, s, v)) => {
println!("Change color to hue {0}, saturation {1}, value {2}", h, s, v)
}
Message::ChangeColor(Color::Rgb(r, g, b)) => {
println!("Change the color to red {0}, green {1}, and blue {2}", r, g, b)
}
_ => (),
}
}
```
![image-20230831154655279](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230831154655279.png)
#### 解构结构体和元组
我们可以以更复杂的方式合成、匹配和嵌套解构模式。我们在一个元组中嵌套了结构体和元组,并将所有的原始值解构出来:
```rust
struct Point {
x: i32,
y: i32,
}
fn main() {
let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 });
println!("{}, {}, {}, {}", feet, inches, x, y);
}
// 3, 10, 3, -10
```
#### 解构引用
解构引用通常用于解构结构体、元组或其他自定义类型的引用,以便访问其字段或元素。
```rust
fn main() {
struct Point {
x: i32,
y: i32,
}
let point = Point { x: 5, y: 10 };
let reference = &point;
match reference {
&Point { x, y } => {
println!("x: {}, y: {}", x, y);
}
}
}
// x: 5, y: 10
```
![image-20230901150749764](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230901150749764.png)
### 忽略模式中的值
#### 使用_来忽略整个值
```rust
fn foo(_: i32, y: i32) {
println!("This code only uses the y parameter: {}", y);
}
fn main() {
foo(3, 4);
}
```
#### 使用嵌套的_忽略部分值
下面场景为:用户不应该被允许覆盖现有设置的自定义值。故并不在乎值是什么:
```rust
let mut setting_value = Some(5);
let new_setting_value = Some(10);
match (setting_value, new_setting_value) {
(Some(_), Some(_)) => {
println!("Can't overwrite an existing customized value");
}
_ => {
setting_value = new_setting_value;
}
}
println!("setting is {:?}", setting_value);
```
![image-20230831163512803](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230831163512803.png)
仅当 `setting_value` 为 None时,才可以赋值。当 `setting_value``new_setting_value` 都为实际值时会打印 `Can't overwrite an existing customized value`。`new_setting_value` 为 None 会重置 `setting_value`
![image-20230831162408620](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230831162408620.png)
我们还可以在一个模式中的多个位置使用下划线来忽略特定值。
```rust
let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, _, third, _, fifth) => {
println!("Some numbers: {}, {}, {}", first, third, fifth);
}
}
// Some numbers: 2, 8, 32
```
#### 通过在名字前以一个下划线开头来忽略未使用的变量
```rust
fn main() {
let _x = 5;
let y = 10;
}
```
这时只会产生一个警告
```rust
warning: unused variable: `y`
--> src\main.rs:3:9
|
3 | let y = 10;
| ^ help: if this is intentional, prefix it with an underscore: `_y`
|
= note: `#[warn(unused_variables)]` on by default
warning: `ignore` (bin "ignore") generated 1 warning (run `cargo fix --bin "ignore"` to apply 1 suggestion)
```
1. **下划线 `_`**
- 使用单独的下划线 `_` 作为变量名,它表示一个未使用的变量或占位符变量,并且**不会将值绑定到该变量**。它在模式匹配中用于表示不关心的情况,以及避免未使用变量的警告。
- 示例:`let _ = 5;` 表示创建一个未使用的变量,但不会绑定任何值。
2. **下划线开头的变量 `_x`**
- 使用 `_x` 这样以下划线开头的变量名,它仍然是一个有效的变量名,并且**会将值绑定到该变量**。但在命名惯例中,以 `_` 开头的变量名通常用于表示未使用的变量或占位符。这使得 `_x` 的用途和含义更明确。
- 示例:`let _x = 5;` 表示创建一个变量 `_x`,并将值 `5` 绑定到该变量。
```rust
fn main() {
let s = Some(String::from("Hello!"));
if let Some(_s) = s {
println!("found a string");
}
// println!("{:?}", s); // ^ value borrowed here after partial move
}
```
```rust
fn main() {
let s = Some(String::from("Hello!"));
if let Some(_) = s {
println!("found a string");
}
println!("{:?}", s);
}
// found a string
// Some("Hello!")
```
#### 用 .. 忽略剩余值
结构体中**不能使用 `..` 跳过前面的字段**,需要显式列出每个字段的模式匹配。而元组可以使用 `..` 语法来跳过前面的元素。若要取结构体第二个值 y,也需要显示写出前面的字段 x。
```rust
struct Point {
x: i32,
y: i32,
z: i32,
}
let origin = Point { x: 0, y: 0, z: 0 };
match origin {
Point { x, .. } => println!("x is {}", x),
// Point { x, y ,..} => println!("x is {}", y),
}
```
![image-20230831172553422](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230831172553422.png)
当仅需要元组的第一个和最后一个值时:
```rust
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, .., last) => {
println!("Some numbers: {first}, {last}");
}
}
}
```
![image-20230831172050798](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230831172050798.png)
使用 .. 必须是无歧义的
```rust
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(.., second, ..) => {
println!("Some numbers: {}", second)
},
}
}
```
变量名`second`对 Rust 来说没有任何特殊意义,若运行此代码会产生错误:
```rust
error: `..` can only be used once per tuple pattern
--> src\main.rs:5:22
|
5 | (.., second, ..) => {
| -- ^^ can only be used once per tuple pattern
| |
| previously used here
```
### 匹配守卫(Match Guards)和额外条件
匹配守卫是在模式匹配的分支中额外添加的条件判断。它允许我们对模式进行更复杂的判断,以选择特定的分支。匹配守卫可以使用分支中定义的变量,并且可以用于处理无法直接在模式中表达的条件逻辑。
```rust
fn main() {
let num = Some(4);
match num {
Some(x) if x % 2 == 0 => println!("The number {} is even", x),
Some(x) => println!("The number {} is odd", x),
None => (),
}
}
// The number 4 is even
```
使用匹配守卫来解决模式遮蔽问题:
```rust
fn main() {
let x = Some(5);
let y = 10;
match x {
Some(50) => println!("Got 50"),
Some(n) if n == y => println!("Matched, n = {n}"),
_ => println!("Default case, x = {:?}", x),
}
println!("at the end: x = {:?}, y = {y}", x);
}
// Default case, x = Some(5)
// at the end: x = Some(5), y = 10
// 当 let x = Some(10); :
// Matched, n = 10
// at the end: x = Some(10), y = 10
```
匹配守卫中的 `if n == y` 并不是一个模式,因此不会引入新变量。这里的 y 是外部的 y,而不是一个新的遮蔽 y,我们可以通过比较 n(`x` 中的值) 和 y 来查找具有与外部 y 相同值的值。
在匹配守卫中可以使用逻辑或运算符 `| `来指定`多个模式`;匹配守卫的条件将适用于所有这些模式。
```rust
fn main() {
let x = 5;
let y = false;
match x {
4 | 5 | 6 if y => println!("yes"),
_ => println!("no"),
}
}
// no
```
`if`条件适用于整个模式 `4 | 5 | 6`,而不仅仅是最后的值 6。
### @ 绑定
`@` 操作符允许我们同时在测试值是否匹配模式的同时创建一个变量来持有这个值。
```rust
fn main() {
enum Message {
Hello { id: i32 },
}
let msg = Message::Hello {
id: 5
};
match msg {
Message::Hello {
id: id_variable @ 3..=7,
} => println!("Found an id in range: {}", id_variable),
Message::Hello {
id: 10..=12
} => { println!("Found an id in another range") }
Message::Hello {id}
=> println!("Found some other id: {}", id),
}
}
```
![image-20230901144604952](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230901144604952.png)
第一个分支通过在范围 3..=7 前指定 `id_variable @`,捕获了任何匹配此范围的值,同时将其打印了出来。在第二个分支中模式代码无法使用 id 字段中的值,因为我们没有将 id 值保存在变量中。在最后一个分支中,任何值都将与此模式匹配。
使用 @ 允许我们在一个模式中测试一个值并将其保存在一个变量中。

@ -0,0 +1,904 @@
## rust高级特征
1. Unsafe Rust:如何选择退出Rust的某些保证,并承担手动维护这些保证的责任。
2. Advanced traits:关联类型、默认类型参数、完全合格的语法、超特性以及与特性相关的新类型模式。
3. Advanced types:有关新类型模式的更多信息、类型别名、never类型以及动态大小类型。
4. Advanced functions and closures:函数指针和返回闭包。
5. Macros:定义在编译时定义更多代码的方式。
## Unsafe Rust
Unsafe Rust(Unsafe Rust)是Rust编程语言中的一种特性,它允许程序员绕过Rust的内存安全性保证,执行一些可能不安全的操作,以获取更大的灵活性和控制力。它的存在主要有以下两个重要意义:
1. **灵活性和控制力**:Rust的设计目标之一是提供内存安全性和线程安全性,以避免常见的程序错误,如空指针引用和数据竞争。然而,有些情况下,程序员需要执行一些底层、不安全的操作,例如直接操作内存、调用未经验证的外部函数或与底层硬件进行交互。不安全Rust允许程序员在这些情况下绕过编译器的检查,以完成特定任务,这提供了更大的灵活性和控制力。
2. **与底层硬件和外部代码的互操作性**:计算机硬件和操作系统通常不提供与Rust的内存安全性保证完全一致的保障。为了实现低级系统编程、调用C语言函数、编写操作系统内核或执行其他与硬件或外部库交互的任务,程序员可能需要使用不安全Rust。这种情况下,不安全Rust允许将Rust代码与不受Rust安全性检查的外部代码集成在一起,从而实现互操作性。
Unsafe Rust是一种强大但潜在危险的功能,用于处理那些正常情况下Rust会拒绝的操作。它的存在是为了平衡Rust的安全性和控制性,以便在需要时能够执行底层、不安全的任务,同时提供了严格的规则和标准,以减小潜在的风险。程序员在使用不安全Rust时需要格外小心,确保正确地管理内存和资源,以避免潜在的问题。
### 不安全的超能力
可以通过 `unsafe` 关键字来切换到 Unsafe Rust,然后开始一个包含不安全代码的新块。在 Unsafe Rust中,可以执行五个在安全的Rust中无法执行的操作:
1. 解引用裸指针
2. 调用不安全的函数或方法
3. 访问或修改可变的静态变量
4. 实现不安全的trait
5. 访问 `union` 的字段
#### unsafe 关键字
`unsafe` 关键字并不会关闭 Rust 的借用检查器(borrow checker)或禁用其他的安全性检查。使用 `unsafe` 关键字只是可以使用上面的五个超能力。
`unsafe` 关键字并不是表示代码一定是危险的,而是表示这部分代码可能包含潜在的风险,应仔细考虑何时使用 `unsafe` 关键字,并尽量将不安全操作限制在最小的范围内,以降低潜在风险。
### 解引用裸指针
`裸指针`(raw pointers)是指在Rust借用系统的严格控制之外的指针。这些指针是原始的,直接指向内存地址,不提供安全性保证,因此可能引发"悬垂引用"(dangling references)问题。在Rust中,存在两种类型的裸指针:
1. `*const T`:这是一个不可变的裸指针,只能读不能修改。不可变裸指针与C/C++中的`const`指针类似。
2. `*mut T`:这是一个可变的裸指针,可读取数据并且修改。
与引用和智能指针不同,原始指针具有以下特点:
1. 可以忽略借用规则,允许同时存在不可变指针和可变指针,或多个可变指针指向同一位置。
2. 不保证指向有效内存。
3. 允许为空指针。
4. 不实现任何自动清理操作。
```rust
fn main() {
let mut num = 5;
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;
}
```
![image-20230905155241967](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230905155241967.png)
根据上面代码可知:**裸指针可以在安全代码中创建**。
下面创建一个不能确定其有效性的裸指针。尝试使用任意内存是未定义行为:该地址可能包含数据,也可能不包含数据,编译器可能会优化代码以避免内存访问,或者程序可能会因分段错误而出错。
```rust
let address = 0x012345usize;
let r = address as *const i32;
// 未知代码
```
![image-20230906155325690](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230906155325690.png)
在使用裸指针指向的值时,可能会出现问题,故此时需要写在 `unsafe` 块中。
### 调用 Unsafe 函数或方法
unsafe 函数和方法相较于普通函数和方法,其在 fn 的之前有一个额外的 unsafe 关键字。
```rust
fn main() {
unsafe {
dangerous();
}
}
unsafe fn dangerous() {
println!("dangerous");
}
```
![image-20230906171038004](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230906171038004.png)
与使用裸指针类似,调用`unsafe`函数时,需要将其放在`unsafe`块中。
### 创建unsafe代码的安全抽象
为了避免将一个包含不安全代码的函数标记为不安全,将不安全代码封装在一个安全函数内是一种常见的抽象方法。下面以标准库中的 `split_at_mut` 函数为例,此函数包含 `unsafe` 代码。
`split_at_mut`:将一个可变切片分成两部分,分割点在索引位置。第一个部分将包含所有从[0, mid)范围内的索引(不包括索引mid本身),第二个部分将包含所有从[mid, len)范围内的索引(不包括索引len本身)。
```rust
#[stable(feature = "rust1", since = "1.0.0")]
#[inline]
#[track_caller]
#[must_use]
#[rustc_const_unstable(feature = "const_slice_split_at_mut", issue = "101804")]
pub const fn split_at_mut(&mut self, mid: usize) -> (&mut [T], &mut [T]) {
assert!(mid <= self.len());
// SAFETY: `[ptr; mid]` and `[mid; len]` are inside `self`, which
// fulfills the requirements of `from_raw_parts_mut`.
unsafe { self.split_at_mut_unchecked(mid) }
}
```
运行测试代码:
```rust
fn main() {
let mut v = vec![1, 2, 3, 4, 5, 6];
let r = &mut v[..];
let (a, b) = r.split_at_mut(3);
println!("{:?}", a);
println!("{:?}", b);
}
// [1, 2, 3]
// [4, 5, 6]
```
下面仿照 `split_at_mut` 方法,在安全的 rust 中实现一个类似的函数。
```rust
pub const fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = values.len();
assert!(mid <= len);
(&mut values[..mid], &mut values[mid..])
}
```
![image-20230908163342165](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230908163342165.png)
Rust 借用检查器只知道我们两次从同一个切片进行借用,并不知道两个切片并不重叠。此时就是使用 `Unsafe Rust`的时候了。修改代码:
```rust
fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = values.len();
let ptr = values.as_mut_ptr();
assert!(mid <= len);
unsafe {
(
slice::from_raw_parts_mut(ptr, mid),
slice::from_raw_parts_mut(ptr.add(mid), len - mid),
)
}
}
```
![image-20230908170758172](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230908170758172.png)
[as_mut_ptr](https://doc.rust-lang.org/std/primitive.slice.html#method.as_mut_ptr) 函数返回一个指向切片缓冲区的不安全可变指针。此指针的操作通常需要放在 `unsafe` 块内,允许对切片中的元素进行直接的读写操作。
[slice::from_raw_parts_mut](https://doc.rust-lang.org/std/slice/fn.from_raw_parts_mut.html) 函数用于从一个原始的可变指针和一个长度创建一个可变切片(`&mut [T]`),同时此函数是不安全的,因为它依赖于调用者提供有效的指针和长度,并且不进行边界检查。
下面使用 `slice::from_raw_parts_mut` 函数从一个未知的内存位置,并创建一个包含10,000个项目的切片。
```rust
use std::slice;
fn main() {
let address = 0x01234usize;
let r = address as *mut i32;
let values: &[i32] = unsafe{
slice::from_raw_parts_mut(r, 10000)
};
// for i in values {
// println!("{}", i);
// }
// println!("slice len is {}", values.len());
}
```
当试图访问 value 内容时,这个地址可能指向任何内容,包括无效的、未初始化的、或者与 `i32` 类型不匹配的数据。
![image-20230918173012089](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230918173012089.png)
此错误是一个访问违例(Access Violation)错误,通常表明你的程序在试图访问无效的内存位置时崩溃了。但若只打印切片的长度,则此代码是安全的,因为这不涉及访问切片的内容。
#### 使用外部函数与外部代码交互
`extern` 关键字在 Rust 中用于声明外部函数和接口,主要用于与其他编程语言进行交互。它简化了创建和使用外部函数接口(Foreign Function Interface,FFI)的过程。通过 FFI,Rust 可以调用其他语言编写的函数,并允许其他语言调用 Rust 编写的函数。它允许 Rust 与现有的 C、C++、Python 等库和代码进行集成。[具体操作](https://doc.rust-lang.org/nomicon/ffi.html)
```rust
extern "C"{
fn abs(input: i32) -> i32;
}
fn main() {
unsafe {
println!("Absolute value of -3 according to C: {}", abs(-3));
}
}
// Absolute value of -3 according to C: 3
```
在 Rust 中,通常使用 `extern "C"` 来声明与 C 语言兼容的外部函数接口。`abs` 函数来自 C 标准库(C Standard Library)。 Rust 内置了对 C 标准库的支持。下面是开发rust共享库的方式:
```
#[no_mangle]
pub extern "C" fn call_from_c() {
println!("Just called a Rust function from C!");
}
```
\#[no_mangle]注解,告诉Rust编译器不要对该函数的名称进行重命名。
### 访问或修改可变静态变量
rust支持静态变量(全局变量),但是在所有权规则下可能会引发问题,即:如果两个线程访问同一个可变的全局变量,这可能会导致数据竞争。全局静态变量的声明与使用如下:
```rust
static HELLO_WORLD: &str = "Hello, world!";
//等价于 static HELLO_WORLD: &'static str = "Hello, world!";
fn main() {
println!("name is: {}", HELLO_WORLD);
}
```
静态变量的名称采用 `SCREAMING_SNAKE_CASE`。静态变量只能存储生命周期为`'static` 的引用,Rust编译器可以推断生命周期。可变静态变量:
```rust
static mut COUNTER: u32 = 0;
fn add_to_count(inc: u32) {
unsafe {
COUNTER += inc;
}
}
fn main() {
add_to_count(3);
unsafe {
println!("COUNTER: {}", COUNTER);
}
}
// COUNTER: 3
```
任何读写静态变量的代码必须位于`unsafe`块内。在多线程下可变静态变量很难保证不出现数据竞争,需搭配线程安全智能指针来使用。
**常量和静态变量的区别**:
1. **内存地址固定性**:
- 常量:常量在编译时被内联到代码中,没有固定的内存地址。
- 静态变量:静态变量在内存中具有固定的地址,因此可以在不同的地方引用相同的内存位置。
2. **可变性**:
- 常量:常量始终是不可变的,不能被修改。
- 静态变量:静态变量可以是可变的,但访问和修改可变的静态变量被认为是不安全的操作,需要特殊的注意和安全保障。
### 实现不安全 Trait
当一个trait的至少一个方法具有编译器无法验证的不变量(invariant)时,该trait被视为不安全。
```rust
unsafe trait Foo {
// methods go here
}
unsafe impl Foo for i32 {
// method implementations go here
}
fn main() {}
```
![image-20230927170609231](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230927170609231.png)
使用`unsafe impl` 表示我们承诺会维护编译器无法验证的一些安全性规则。例如,如果我们希望将一个类型标记为 `Send` 或` Sync`,但这个类型包含了编译器无法验证的操作(比如使用了原始指针等),那么我们必须使用 `unsafe` 来表明我们会手动确保这些操作的安全性,因为 Rust 无法自动验证它们。
### 访问 `union` 的字段
只有使用 `unsafe` 才能访问 union 的字段,因为 Rust 无法保证当前存储在union 实例中的数据的类型。union类似于结构体,但在特定实例中声明一个字段。[the Rust Reference](https://doc.rust-lang.org/reference/items/unions.html).
```rust
union Value {
integer: i32,
floating: f32,
boolean: bool,
}
fn main() {
// let mut my_value = Value { integer: 1, floating: 2f32}; union expressions should have exactly one field
let mut my_value = Value { integer: 1};
// 在这里,我们可以访问联合体的一个字段
unsafe {
println!("Integer value: {}", my_value.integer);
// println!("floating value: {}", my_value.floating);
// println!("boolean value: {}", my_value.boolean);
}
}
```
用法上其实是可以访问没有赋值的字段的:![image-20230927172855166](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230927172855166.png)
## 高级特征
### 关联类型在 trait 定义中指定占位符类型
**关联类型**(*associated types*)是一个将类型占位符与 trait 相关联的方式,这样 trait 的方法签名中就可以使用这些占位符类型。trait 的实现者将为特定的实现指定具体类型,以替代占位类型。这样一来,我们可以定义一个特性,它使用某些类型,而无需在实现该特性之前确切知道这些类型是什么。
标准库中的 `Iterator` trait:
```rust
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
```
类型 `Item` 是一个占位符,而next方法的定义显示它将返回类型为 `Option Self::Item` 的值。实现 `Iterator` 特性的类型将为 `Item` 指定具体类型,并且`next` 方法将返回一个包含该具体类型值的 `Option`。实现了 `Iterator` 特性的类型能够通过 `for` 循环来进行**自动迭代**(`next`方法)。
关联类型看起来与同泛型相似,泛型允许我们定义一个函数而不指定它可以处理的类型。但实际上二者是存在差别的:使用泛型时我们必须在每个实现中标注类型,而使用关联类型时不需要标注类型,因为不能多次在类型上实现一个特性。
![image-20231008141032673](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20231008141032673.png)
![image-20231008141900271](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20231008141900271.png)
### 默认泛型类型参数和运算符重载
当使用泛型类型参数时,可以为泛型类型指定默认的具体类型,指定默认类型的语法是在声明泛型类型时使用 `<PlaceholderType=ConcreteType>`,此技术一般用于运算符重载。
Rust 并不允许创建自定义运算符或重载任意运算符,不过 `std::ops` 中所列出的运算符和相应的 trait 可以通过实现运算符相关 trait 来重载。
![image-20231008162803489](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20231008162803489.png)
`Add` trait 的定义:
```rust
trait Add<RHS=Self> {
type Output;
fn add(self, rhs: RHS) -> Self::Output;
}
```
根据 **默认类型参数** `RHS=Self`,可知,add 方法默认会接受一个相同类型的值。下面修改 **默认类型参数** 为特定类型:
![image-20231008163823188](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20231008163823188.png)
### 调用相同名称的方法
Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法,也不能阻止为同一类型同时实现这两个 trait。下面两个 trait 都有一个fly方法,Human 本身也实现了一个名为fly的方法。
```rust
struct Human;
trait Pilot {
fn fly(&self);
}
trait Wizard {
fn fly(&self);
}
impl Pilot for Human {
fn fly(&self) {
println!("This is your captain speaking.");
}
}
impl Wizard for Human {
fn fly(&self) {
println!("UP!");
}
}
impl Human {
fn fly(&self) {
println!("*waving arms furiously*");
}
}
fn main() {
let person = Human;
person.fly();
}
// *waving arms furiously*
```
调用fly方法会执行 Human 类型上的fly方法,若 Human 类型上没有fly方法,会产生运行时错误:
![image-20231025091517095](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20231025091517095.png)
调用 trait 方法的语法如下,
```rust
fn main() {
let person = Human;
Pilot::fly(&person);
Wizard::fly(&person);
Human::fly(&person);
person.fly();
}
// This is your captain speaking.
// UP!
// *waving arms furiously*
// *waving arms furiously*
```
当存在多个类型或trait定义了具有相同函数名称的 **关联函数****静态函数** 时,需使用完全限定语法。
```rust
trait Animal {
fn baby_name() -> String;
}
struct Dog;
impl Dog {
fn baby_name() -> String {
String::from("Spot")
}
}
impl Animal for Dog {
fn baby_name() -> String {
String::from("puppy")
}
}
fn main() {
println!("A baby dog is called a {}", Dog::baby_name());
// println!("A baby dog is called a {}", Animal::baby_name());
// cannot call associated function on trait without specifying the corresponding `impl` type
println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
}
// A baby dog is called a Spot
// A baby dog is called a puppy
```
关联函数无法通过 trait::function(&person) 来调用,因没有 self 参数无法通过编译。需通过
完全限定语法。
```rust
<Type as Trait>::function(receiver_if_method, next_arg, ...);
```
### 父 trait 使用另一特征的函数
![image-20231029173931077](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20231029173931077.png)
Point 若想使用 OutlinePrint 的 outline_print方法,必须同时实现 Display trait。
### newtype 模式用以在外部类型上实现外部 trait
<u>孤儿规则是 Rust 语言的一项规则,规定只有在当前 crate 中定义的类型或特征,或者在标准库中的类型,才能相互实现特征。</u>因此,我们无法直接为 `Vec ` 上实现 `Display`, 但是可通过 **newtype 模式**(*newtype pattern*),绕开这个限制的。
![image-20231029175306233](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20231029175306233.png)
## 高级类型
### 为了类型安全和抽象而使用 newtype 模式
1. **静态类型安全:** 新类型模式可以确保不同的包装类型不会混淆,从而提供了静态类型安全。
2. **表示值的单元:** 新类型模式可以用于表示特定值的单位,例如米和毫米。
3. **抽象实现细节:** 新类型可以用于隐藏内部实现细节,暴露不同的公共 API,从而提供了封装和抽象。
4. **隐藏泛型类型:** 新类型也可以用于隐藏内部的泛型类型,从而提供更好的类型抽象。
### 类型别名用来创建类型同义词
可以使用 `type` 关键字来为现有类型提供另一个名称。
```
fn main() {
type Kilometers = i32; // i32 类型别名
let x: i32 = 5;
let y: Kilometers = 5;
println!("x + y = {}", x + y);
}
// x + y = 10
```
利用类型别名减少冗长代码。
```rust
fn main() {
type Thunk = Box<dyn Fn() + Send + 'static>;
let f: Thunk = Box::new(|| println!("hi"));
fn takes_long_type(f: Thunk) {
// --snip--
}
fn returns_long_type() -> Thunk {
// --snip--
Box::new(|| ())
}
}
```
利用类型别名通常还与`Result<T, E>`类型一起用于减少重复。
```
use std::fmt;
use std::io::Error;
// pub trait Write {
// fn write(&mut self, buf: &[u8]) -> Result<usize, Error>;
// fn flush(&mut self) -> Result<(), Error>;
//
// fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>;
// fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Error>;
// }
type Result<T> = std::result::Result<T, Error>;
pub trait Write {
fn write(&mut self, buf: &[u8]) -> Result<usize>;
fn flush(&mut self) -> Result<()>;
fn write_all(&mut self, buf: &[u8]) -> Result<()>;
fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<()>;
}
```
> Rust 中的类型别名(Type Aliases)和 C++ 中的 `typeof` 运算符有一些相似之处,但也有一些重要的区别。
>
> **相似之处**
>
> 1. **类型重命名**:两者都涉及将类型重命名为另一个名称,以提高代码的可读性和模块化。
> 2. **减少冗余**:它们都有助于减少代码中的冗余,特别是在处理长或复杂的类型名称时。
>
> **区别**
>
> 1. **语法和用途**
> - Rust 的类型别名是一种语言特性,使用 `type` 关键字来声明,主要用于提高代码的可读性和模块化。它在编译时起作用,不提供运行时类型信息。
> - C++ 的 `typeof` 运算符是用于获取表达式的实际类型,它在运行时提供类型信息。它是一种运行时机制,通常用于编写与类型相关的通用代码。
> 2. **类型信息**
> - Rust 的类型别名不提供对类型信息的访问,它只是一种更改类型名称的方式。
> - C++ 的 `typeof` 运算符用于检索表达式的实际类型信息,允许程序员在运行时获取有关类型的信息。
> 3. **编程范式**
> - Rust 的类型别名主要用于静态类型检查,与Rust的静态类型系统一起使用。
> - C++ 的 `typeof` 运算符更适用于传统的动态类型语言,用于在运行时处理类型信息。
>
> 总的来说,尽管它们都涉及到类型的更名或别名,但 Rust 中的类型别名主要用于提高代码可读性和模块化,而 C++ 中的 `typeof` 运算符用于运行时获取类型信息。这些是两种不同的机制,适用于不同的编程语言和用途。
### never 类型
Rust 有一个叫做`!`的特殊类型。它没有任何值也被称为空类型(empty type),返回值为`!`的函数称为发散函数。
```rust
fn bar() -> ! {
// --snip--
panic!();
}
```
`continue` 为`!`类型。match分支必须返回相同的类型,而`!`类型的表达式可以强制转换为任何其他类型,故可以在match中使用。
```rust
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
```
`panic!`同样也是`!`类型,因此可以在match中使用。
```rust
enum Option<T> {
Some(T),
None,
}
use crate::Option::*;
impl<T> Option<T> {
pub fn unwrap(self) -> T {
match self {
Some(val) => val,
None => panic!("called `Option::unwrap()` on a `None` value"),
}
}
}
```
`loop` 表达式也是`!`类型。
```
fn main() {
print!("forever ");
loop {
print!("and ever ");
}
}
```
因为循环永远也不结束,所以此表达式的值是 `!`
### 动态大小类型和 `Sized` trait
在Rust中,动态大小类型「DST」指的是具有在运行时才能确定大小的类型,例如str。这些类型的大小在编译时无法确定,因此不能创建直接包含这些类型的变量。Rust要求所有类型在编译时都有已知的大小,以便为值分配足够的内存。
```、
fn main() {
let s1: str = "Hello there!";
let s2: str = "How's it going?";
//error[E0308]: mismatched types
}
```
因为str是一个DST,其大小在编译时无法确定。这会导致编译错误(error[E0308]: mismatched types),因为Rust要求所有类型在编译时必须具有已知的大小。需使用&str代替str。slice 数据结构仅存储slice的起始位置和长度。
动态大小类型的黄金规则:**必须将动态大小类型的值置于某种指针之后**。为了处理 DST,Rust 有一个特定的 trait 来决定一个类型的大小是否在编译时可知:这就是 `Sized` trait。这个 trait 自动为编译器在编译时就知道大小的类型实现。
## 高级函数与闭包
### 函数指针
通过函数指针允许我们使用函数作为另一个函数的参数。函数的类型是 `fn` (使用小写的 「f」 )以免与 `Fn` 闭包 trait 相混淆。`fn` 被称为 **函数指针**(*function pointer*)。
```
fn add_one(x: i32) -> i32 {
x + 1
}
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
f(arg) + f(arg)
}
fn main() {
let answer = do_twice(add_one, 5);
println!("The answer is: {}", answer);
}
// The answer is: 12
```
与闭包不同,fn 是一种类型而不是特性,因此我们直接指定 fn 作为参数类型,而不是声明具有 Fn 特性之一的通用类型参数作为特性约束。
函数指针实现了闭包 trait(Fn、FnMut 和 FnOnce)中的所有三种,因此可以将函数指针作为预期闭包的参数传递给函数。当与不支持闭包的外部代码进行交互时,C 函数可以接受函数作为参数,但 C 不支持闭包。
### 返回闭包
闭包表现为 trait,这意味着不能直接返回闭包。
```rust
fn main() {
println!("- - -");
}
fn returns_closure() -> dyn Fn(i32) -> i32 {
|x| x + 1
}
// error[E0746]: return type cannot have an unboxed trait object
// --> src\main.rs:4:25
// |
// | fn returns_closure() -> dyn Fn(i32) -> i32 {
// | ^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
```
根据错误提示可知 Rust 不知道存储闭包需要多少空间。修改代码:
```rust
fn main() {
let closure = returns_closure();
let result = closure(5);
println!("Result: {}", result);
}
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
Box::new(|x| x + 1)
}
// Result: 6
```
## 宏
在Rust中,宏(macro)是一组功能,包括用 `macro_rules!` 定义的声明式宏和三种类型的过程宏:
- 自定义 `#[derive]` 宏,用于通过结构体和枚举上的 `derive` 属性生成代码。
- 类属性的宏,用于定义可用于任何项上的自定义属性。
- 类函数的宏,看起来像函数调用,但操作其参数中指定的标记。
### 宏和函数的区别
从根本上讲,宏是一种编写生成其他代码的代码的方式,这被称为元编程。元编程对于减少需要编写和维护的代码量非常有用,这也是函数的作用之一。宏和函数在 Rust 中有一些重要的区别:
1. **代码生成和操作能力**:宏允许你生成和操作代码,而函数则执行特定的操作。宏通常用于代码生成、元编程和在编译期间进行代码操作,而函数则用于在运行时执行操作。
2. **参数数量和类型**:函数必须具有已知数量和类型的参数,而宏可以接受可变数量的参数,包括不同类型的参数。这使得宏能够更加灵活地处理各种输入。
3. **编译时与运行时**:宏在编译时展开,生成代码,而函数在运行时执行。这使得宏能够执行一些函数无法完成的任务,如实现 trait 或生成代码。
4. **作用范围**:你必须在使用宏之前先定义或引入它们,而函数可以在代码的任何位置定义和调用。
5. **复杂性和可读性**:宏定义通常比函数定义更复杂,因为你在编写代码来生成代码。这可能会降低宏的可读性和维护性。
### 使用 macro_rules! 声明宏
Rust 最常用的宏形式是 声明宏(declarative macros),它允许你以类似 Rust 的 match 表达式的方式编写宏。下面为vec! 的宏定义:
```rust
#[cfg(all(not(no_global_oom_handling), not(test)))]
#[macro_export]
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_diagnostic_item = "vec_macro"]
#[allow_internal_unstable(rustc_attrs, liballoc_internals)]
macro_rules! vec {
() => (
$crate::__rust_force_expr!($crate::vec::Vec::new())
);
($elem:expr; $n:expr) => (
$crate::__rust_force_expr!($crate::vec::from_elem($elem, $n))
);
($($x:expr),+ $(,)?) => (
$crate::__rust_force_expr!(<[_]>::into_vec(
// This rustc_box is not required, but it produces a dramatic improvement in compile
// time when constructing arrays with many elements.
#[rustc_box]
$crate::boxed::Box::new([$($x),+])
))
);
}
```
以下是它的主要部分:
- `macro_rules! vec`:这是宏定义的开始。
- `()` 模式:当宏没有参数时,生成一个新的空向量。
- `($elem:expr; $n:expr)` 模式:当宏接受两个参数时,根据给定的元素表达式 `$elem` 和元素数量 `$n`,生成一个新的向量。
- `($($x:expr),+ $(,)?)` 模式:当宏接受一个或多个元素表达式时,生成一个新的向量。这是宏的最常见用法。
### 用于从属性生成代码的过程宏
过程宏接收 Rust 代码作为输入,在这些代码上进行操作,然后产生另一些代码作为输出,而非像声明式宏那样匹配对应模式然后以另一部分代码替换当前代码。过程宏的定义通常位于它们自己的 crate 中,而不是直接在你的应用程序中。
```
use proc_macro;
#[some_attribute] //占位符
pub fn some_name(input: TokenStream) -> TokenStream {
}
```
定义过程宏的函数以 `TokenStream` 作为输入并产生 `TokenStream` 作为输出。在同一crate 中可以有多种类型的过程宏。
### 如何编写自定义 derive 宏
首先创建以下内容:
![image-20231029233804977](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20231029233804977.png)
下一步是定义过程宏。在hello_macro中创建名为 hello_macro_derive 的新 crate,并修改其 Cargo.toml:
![image-20231029234419670](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20231029234419670.png)
并添加如下内容到 `hello_macro_derive` crate 的 `src/lib.rs` 文件中,`hello_macro_derive` 函数函数负责解析 `TokenStream`
```rust
use proc_macro::TokenStream;
use quote::quote;
use syn;
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
// Construct a representation of Rust code as a syntax tree
// that we can manipulate
let ast = syn::parse(input).unwrap();
// Build the trait implementation
impl_hello_macro(&ast)
}
```
`hello_macro_derive` 函数将在我们的库的用户在类型上指定 `#[derive(HelloMacro)]` 时被调用。同时`syn` crate 将 Rust 代码从字符串解析为数据结构,我们可以对其执行操作。`quote` crate 将 `syn` 数据结构转换回 Rust 代码。`impl_hello_macro` 负责生成新的 Rust 代码。
```rust
fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let gen = quote! {
impl HelloMacro for #name {
fn hello_macro() {
println!("Hello, Macro! My name is {}!", stringify!(#name));
}
}
};
gen.into()
}
```
[DeriveInput](https://docs.rs/syn/latest/syn/struct.DeriveInput.html#) struct定义如下:
```rust
pub struct DeriveInput {
pub attrs: Vec<Attribute>,
pub vis: Visibility,
pub ident: Ident,
pub generics: Generics,
pub data: Data,
}
```
`hello_macro_derive` crate 代码截图:
![image-20231030000316245](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20231030000316245.png)
运行图以及相关依赖:
![image-20231030000430564](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20231030000430564.png)
### 类属性宏
类属性宏(Attribute-like macros)与自定义派生宏相似,但不是为derive属性生成代码,而是允许你创建新的属性。derive只适用于结构体和枚举;而属性宏也可以应用于其他项,比如函数。
可创建一个名为 route 的属性用于注解 web 的函数:
```
#[route(GET, "/")]
fn index() {
// ...
}
```
这个 #[route] 属性将由框架定义为一个过程宏。宏定义函数的签名如下:
```
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
// ...
}
```
在这里有两个类型为 `TokenStream` 的参数。第一个用于属性的内容:`GET, "/"` 部分。第二个是属性所标记的项,此处为index()。
### 类函数宏
类函数宏定义看起来像函数调用的宏。例如,sql! 宏可能像这样被调用:
```rust
let sql = sql!(SELECT * FROM posts WHERE id=1);
```
`sql!` 宏的定义如下:
```rust
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {
// ...
}
```

@ -0,0 +1,183 @@
# 2
## 输入数字代码
![image-20221227143849645](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221227143849645.png)
```
use std::io;
```
引入io标准库。
```
let mut guess = String::new();
```
let 用于创建变量,但是rust默认情况下,变量的值是不可改变的。为了使值可以变化,需在变量名前添加mut;
```
let mut guess = String::new();
```
行创建了一个可变变量,该变量当前绑定到一个新的空实例`String`
```
io::stdin()
.read_line(&mut guess)
```
使用此io函数,来允许用户进行输入。若没在开头导入use std::io,也可以使用std::io::stdin来完成此操作。
![image-20221227144811136](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221227144811136.png)
&表示此参数是一个引用,它为您提供了一种方法,让您的代码的多个部分访问一个数据,而无需将该数据多次复制到内存中。引用和变量一样默认也是不可变的。因此需要使用&mut guess使其可变,而不是使用&guess。
```
.expect("Failed to read line");
```
read_line将用户输入的任何内容放入我们传递给它的字符串中,但它也会返回一个Result值。Result是一个enumeration,通常称为enum,它是一种可以处于多种可能状态之一的类型。我们称每个可能的状态为变体。
Result的变体是Ok和Err。Okvariant表示操作成功,里面Ok是成功生成的值。Err变体表示操作失败,并Err包含有关操作失败的方式或原因的信息。
如果您不调用expect,程序将编译,但您会收到警告:
![image-20221227150204236](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221227150204236.png)
### 占位符
```
println!("You guessed: {guess}");
```
{}为占位符,打印变量值时,变量名可以放在大括号内。打印表达式的计算结果时,将空大括号放在格式字符串中,然后在格式字符串后面加上逗号分隔的表达式列表。
```
#![allow(unused)]
fn main() {
let x = 5;
let y = 10;
println!("x = {x} and y + 2 = {}", y + 2);
}
// res: x = 5 and y = 12
```
## 生成随机数
### rand crate
crate是Rust源代码文件的集合,rand crate 是一个库crate,其中包含旨在用于其他程序且不能单独执行的代码。
使用carte前,需要修改Cargo.toml为文件来添加rand crate依赖。
![image-20221227152350189](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221227152350189.png)
说明符0.8.5实际上是 的简写^0.8.5,表示任何至少为 0.8.5 但低于 0.9.0 的版本。
![image-20221227155009707](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221227155009707.png)
当我们包含一个外部依赖项时,Cargo 会从registry中获取依赖项所需的所有内容的最新版本,这是来自Crates.io的数据副本。Crates.io 是 Rust 生态系统中的人们发布他们的开源 Rust 项目供其他人使用的地方。
Cargo 知道它可以重用它已经为那些下载和编译的东西,所以第二次build无操作。
#### cargo.lock
Cargo 有一种机制可以确保您或任何其他人每次构建您的代码时都可以重建相同的工件:Cargo 将仅使用您指定的依赖项版本。具体实现是通过Cargo.lock。由于*Cargo.lock*文件,您的项目将保持在 0.8.5,直到您明确升级。
因为*Cargo.lock*文件对于可重现的构建很重要,所以它通常与项目中的其余代码一起签入源代码管理。
![image-20221227155552276](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221227155552276.png)
```rust
cargo update
```
它将忽略Cargo.lock文件并在Cargo.toml中找出符合你的规范的所有最新版本。然后 Cargo 会将这些版本写入Cargo.lock文件。
### 生成随机数
![image-20221227161031139](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221227161031139.png)
```
use rand::Rng
```
Rng特征定义了随机数生成器实现的方法。
```
rand::thread_rng
```
为我们提供我们将要使用的特定随机数生成器的函数:一个在当前执行线程本地并由操作系统播种的随机数生成器。
```
gen_range
```
该 gen_range方法将范围表达式作为参数并生成范围内的随机数。我们在这里使用的范围表达式的形式start..=end是包含下限和上限。
```
cargo doc --open
```
![image-20221227161704399](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221227161704399.png)
可以查看当前项目所有的依赖项文档。
### 比较大小
下面代码存在String和integer不匹配的类型错误,仅是为了方便记录新增内容。
![image-20221227162225237](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221227162225237.png)
```
std::cmp::Ordering
```
该Ordering类型是另一个枚举,具有变体Less、Greater和Equal。当您比较两个值时,这些是可能的三种结果。
match类似switch,会根据 cmp``guess``secret_number 返回不同的结果。
#### 类型转换
![image-20221227170812732](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221227170812732.png)
```
let guess: u32 = guess.trim().parse().expect("Please type a number!");
```
Rust允许我们guess用一个新值来遮盖以前的值,当要将值从一种类型转换为另一种类型时,通常会用到此。
数字类型有u32和i64,故字符串转换为数字时,需要显式说明具体数字类型。用:来指定。
#### 循环
![image-20221227172724375](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221227172724375.png)
循环成功,新增break
![image-20221227172955774](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221227172955774.png)
#### 处理用户输入非数字退出问题
![image-20221227173709707](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221227173709707.png)
我们从一个expect调用切换到一个match表达式,从错误崩溃转移到处理错误。它parse返回一个Result 类型并且Result是一个具有变体Ok和的枚举Err

@ -0,0 +1,438 @@
## 构建一个单线程Web服务器
Web服务器涉及的两个主要协议是超文本传输协议(HTTP)和传输控制协议(TCP)。这两个协议都是请求-响应协议,意味着客户端发起请求,服务器监听请求并提供响应给客户端。这些请求和响应的内容由这些协议定义。
### 监听TCP连接
```rust
use std::net::TcpListener;
fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
for stream in listener.incoming() {
let stream = stream.unwrap();
println!("Connection established!");
}
}
//当访问此端口:
// Connection established!
// Connection established!
// Connection established!
```
### 读取请求
```rust
use std::{
io::{prelude::*, BufReader},
net::{TcpListener, TcpStream},
};
fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
for stream in listener.incoming() {
let stream = stream.unwrap();
handle_connection(stream);
}
}
fn handle_connection(mut stream: TcpStream) {
let buf_reader = BufReader::new(&mut stream);
let http_request: Vec<_> = buf_reader
.lines()
.map(|result| result.unwrap())
.take_while(|line| !line.is_empty())
.collect();
println!("Request: {:#?}", http_request);
}
```
此时当访问端口后,会输出以下内容:
![image-20231030092459777](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20231030092459777.png)
`take_while``for` 循环都可以用于迭代集合中的元素,但它们有不同的用途和行为:
1. `for` 循环:
- `for` 循环是一种通用的迭代工具,它会遍历整个集合,处理每个元素。
- 你可以在 `for` 循环中执行任何你想要的操作,例如处理集合中的每个元素,筛选元素,转换元素等。
- `for` 循环通常用于遍历整个集合,而不是根据特定条件选择性地处理元素。
2. `take_while` 方法:
- `take_while` 是一个迭代器方法,它根据条件选择性地从集合中取元素,并返回一个新的迭代器。
- 它会从集合的开头开始,一直遍历元素,直到条件不再满足为止。
- 通常,`take_while` 用于处理集合中的一部分元素,而不是整个集合。
总之,`for` 循环通常用于遍历整个集合,而 `take_while` 通常用于处理集合的部分元素,具体取决于满足的条件。
### 返回实际 HTML 页面
首先在项目根目录创建一个hello.html文件:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Hello!</title>
</head>
<body>
<h1>Hello!</h1>
<p>Hi from Rust</p>
</body>
</html>
```
修改handle_connection,添加返回信息:
```rust
use std::{
fs,
io::{prelude::*, BufReader},
net::{TcpListener, TcpStream},
};
fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
for stream in listener.incoming() {
let stream = stream.unwrap();
handle_connection(stream);
}
}
fn handle_connection(mut stream: TcpStream) {
let buf_reader = BufReader::new(&mut stream);
let http_request: Vec<_> = buf_reader
.lines()
.map(|result| result.unwrap())
.take_while(|line| !line.is_empty())
.collect();
let status_line = "HTTP/1.1 200 OK";
let contents = fs::read_to_string("hello.html").unwrap();
let length = contents.len();
let response = format!("{}\r\nContent-Length: {}\r\n\r\n{}", status_line, length, contents);
stream.write_all(response.as_bytes()).unwrap();
println!("Request: {:#?}", http_request);
println!("Response: {:#?}", response);
}
```
![image-20231030162924786](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20231030162924786.png)
### 验证请求并选择性响应
根据请求自定义响应,仅对 `/` 路径的请求返回HTML文件。修改handle_connection函数:
```rust
fn handle_connection(mut stream: TcpStream) {
let buf_reader = BufReader::new(&mut stream);
let request_line = buf_reader.lines().next().unwrap().unwrap();
// 只查看HTTP请求的第一行,调用 next 来从迭代器中获取第一个。
println!("Request: {:#?}", request_line);
// let http_request: Vec<_> = buf_reader
// .lines()
// .map(|result| result.unwrap())
// .take_while(|line| !line.is_empty())
// .collect();
if request_line == "GET / HTTP/1.1" {
let status_line = "HTTP/1.1 200 OK";
let contents = fs::read_to_string("hello.html").unwrap();
let length = contents.len();
let response = format!("{}\r\nContent-Length: {}\r\n\r\n{}", status_line, length, contents);
stream.write_all(response.as_bytes()).unwrap();
}else {
// other
}
}
```
效果图:
![image-20231030165417178](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20231030165417178.png)
在根目录添加404页面,当请求不存在时显示:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Hello!</title>
</head>
<body>
<h1>Oops!</h1>
<p>Sorry, I don't know what you're asking for.</p>
</body>
</html>
```
添加else分支代码:
```rust
else {
let status_line = "HTTP/1.1 404 NOT FOUND";
let contents = fs::read_to_string("404.html").unwrap();
let length = contents.len();
let response = format!(
"{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}"
);
stream.write_all(response.as_bytes()).unwrap();
}
```
效果图:
![image-20231030170414132](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20231030170414132.png)
### 重构代码
重构代码,提取重复代码并使404更合理。
```rust
use std::{
fs,
io::{prelude::*, BufReader},
net::{TcpListener, TcpStream},
};
fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
for stream in listener.incoming() {
let stream = stream.unwrap();
handle_connection(stream);
}
}
fn handle_connection(mut stream: TcpStream) {
let buf_reader = BufReader::new(&mut stream);
let request_line = buf_reader.lines().next().unwrap().unwrap();
let (status_line, filename) = if request_line == "GET / HTTP/1.1" {
("HTTP/1.1 200 OK", "hello.html")
}else {
("HTTP/1.1 404 NOT FOUND", "404.html")
};
let contents = fs::read_to_string(filename).unwrap();
let length = contents.len();
let response = format!("{}\r\nContent-Length: {}\r\n\r\n{}", status_line, length, contents);
stream.write_all(response.as_bytes()).unwrap();
}
```
效果图:
![image-20231030171544102](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20231030171544102.png)
## 创建多线程服务器
当前服务器会逐个处理每个请求,在第一个请求处理完成之前,它不会处理第二个连接。
### 模拟慢请求
当执行sleep满请求时,其他请求都不会执行。
![image-20231030172734366](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20231030172734366.png)
### 使用线程池改善吞吐量
**线程池**(*thread pool*)是一组预先分配的等待或准备处理任务的线程。可通过限制线程池中线程的数量,防止耗尽服务器的所有资源,将请求的处理推向停滞。
#### 每个请求创建一个线程(当请求过多时,会耗尽系统资源)
修改main函数:
```rust
fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
for stream in listener.incoming() {
let stream = stream.unwrap();
thread::spawn(|| {
handle_connection(stream);
});
}
}
```
效果图:
![image-20231031103731348](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20231031103731348.png)
#### 使用线程池管理线程
[thread::spawn](https://doc.rust-lang.org/std/thread/fn.spawn.html#) 函数定义:
```rust
#[stable(feature = "rust1", since = "1.0.0")]
pub fn spawn<F, T>(f: F) -> JoinHandle<T>
where
F: FnOnce() -> T,
F: Send + 'static,
T: Send + 'static,
{
Builder::new().spawn(f).expect("failed to spawn thread")
}
```
`where` 子句中每个约束的含义:
1. `F: FnOnce() -> T`: 这个约束表示泛型参数 `F` 必须是一个闭包(函数),并且这个闭包不需要任何参数(`FnOnce()`),它的返回值类型必须与 `T` 相匹配。也就是说,`F` 必须是一个能够执行一次的函数,而返回值类型必须是 `T`
2. `F: Send + 'static`: 这个约束要求泛型参数 `F` 必须实现 `Send` 特性。`Send` 特性表示类型是可以安全地在不同线程之间传递的。此外,`'static` 生命周期表示 `F` 必须具有静态生命周期,也就是说,它的生命周期可以持续到整个程序的运行期间。
3. `T: Send + 'static`: 同样,这个约束要求泛型参数 `T` 必须实现 `Send` 特性并且必须具有静态生命周期。
根据spawn函数参数及返回值,编写以下线程池文件:
```rust
use std::{
sync::{mpsc, Arc, Mutex},
thread,
};
pub struct ThreadPool {
workers: Vec<Worker>,
sender: mpsc::Sender<Job>,
}
type Job = Box<dyn FnOnce() + Send + 'static>;
impl ThreadPool {
pub fn new(size: usize) -> ThreadPool {
assert!(size > 0);
let (sender, receiver) = mpsc::channel();
// sender可以多个、receiver只能有一个,使用Arc在多个线程下使用同一个receiver
let receiver = Arc::new(Mutex::new(receiver));
let mut workers = Vec::with_capacity(size);
for id in 0..size {
workers.push(Worker::new(id,Arc::clone(&receiver)));
}
ThreadPool { workers, sender }
}
pub fn execute<F>(&self, f: F)
where F: FnOnce() + Send + 'static,
{
let job = Box::new(f);
self.sender.send(job).unwrap();
}
}
struct Worker {
id: usize,
thread: thread::JoinHandle<()>,
}
impl Worker {
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
let thread = thread::spawn(move || loop {
let job = receiver.lock().unwrap().recv().unwrap();
println!("Worker {id} got a job; executing.");
job();
});
Worker { id, thread }
}
}
```
主函数代码:
```rust
use std::{
fs,
io::{prelude::*, BufReader},
net::{TcpListener, TcpStream},
thread,
time::Duration,
};
use hello::ThreadPool;
fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
let pool = ThreadPool::new(4);
for stream in listener.incoming() {
let stream = stream.unwrap();
pool.execute(|| {
handle_connection(stream);
});
}
}
fn handle_connection(mut stream: TcpStream) {
let buf_reader = BufReader::new(&mut stream);
let request_line = buf_reader.lines().next().unwrap().unwrap();
println!("request: {}", request_line);
// 使用match匹配多个请求
let (status_line, filename) = match &request_line[..] {
"GET / HTTP/1.1" => ("HTTP/1.1 200 OK", "hello.html"),
"GET /sleep HTTP/1.1" => {
thread::sleep(Duration::from_secs(10));
("HTTP/1.1 200 OK", "hello.html")
}
_ => ("HTTP/1.1 404 NOT FOUND", "404.html")
};
let contents = fs::read_to_string(filename).unwrap();
let length = contents.len();
let response = format!("{}\r\nContent-Length: {}\r\n\r\n{}", status_line, length, contents);
stream.write_all(response.as_bytes()).unwrap();
}
```
new函数会同时创建一组Worker线程,Worker线程在创建后,因发送端无数据会阻塞当前线程,等待直到有数据可用。当执行线程池 execute方法时,job会被发送给接收端,然后Worker线程会竞争获取队列中的任务来执行。
因为Worker的new方法中使用创建了新的线程,所以是异步执行,故for可以继续创建Worker实例而不会阻塞主线程。同时每当一个工作线程完成了它的任务,就会创建一个新的具有相同标识符id的新worker实例,以继续等待任务的到来。运行图:
![image-20231101162658607](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20231101162658607.png)
当发送端有job发送时,以及执行完任务后,效果图:
![image-20231101163138635](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20231101163138635.png)
若使用while-let替换let来实现new:
```rust
impl Worker {
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
let thread = thread::spawn(move || {
while let Ok(job) = receiver.lock().unwrap().recv() {
println!("Worker {id} got a job; executing.");
job();
}
});
Worker { id, thread }
}
}
```
在使用let job = receiver.lock().unwrap().recv().unwrap();的代码中,通过使用let,右侧表达式中使用的任何临时值在let语句结束时会被立即丢弃。然而,while let(以及if let和match)不会丢弃临时值,直到关联块的末尾。while-let中,锁会一直被持有,直到job()函数执行完毕,这会导致其他线程无法获取锁并执行任务。
只有第一个线程在工作,效果图:
![image-20231101172020795](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20231101172020795.png)

@ -0,0 +1,81 @@
# 2379. 得到 K 个黑块的最少涂色次数
### description:
给你一个正整数数组 nums,请你移除 最短子数组(可以为空),使得剩余元素的和能被 p 整除。 不允许 将整个数组都移除。请你返回你需要移除的最短子数组的长度,如果无法满足题目要求,返回 -1 。子数组定义为原数组中连续的一组元素。
代码: minimum_recolors为正确写法。
```rust
fn main() {
let blocks = String::from("WWBWWBBWBW");
let nums = 7usize;
let res = minimum_recolors(&blocks, nums);
println!("minimum_recolors {}", res);
let res = minimum_recolors1(&blocks, nums);
println!("minimum_recolors1 {}", res);
}
// 错误代码
fn minimum_recolors1(blocks: &str, nums: usize) -> usize{
let mut l = 0usize;
let mut r = 0usize;
let mut cnt = 0usize;
let mut blocks = blocks.chars(); // Chars() 为一个字符串类型的迭代器,不是字符数组。没有len方法、
// blocks.len() -- no method named `len` found for struct `Chars` in the current scope
blocks.as_str().len(); //这样可以。
println!("blocks.len() is {}", blocks.as_str().len());
while r < nums {
cnt += if blocks.next() == Some('W') {1} else { 0 };
r += 1;
}
println!("blocks.len() is {}", blocks.as_str().len());
// 使用向量 此时的blocks为字符串迭代器。 如果为字符串应该写成 blocks.chars().collect::<Vec<char>>();
let mut blocks = blocks.collect::<Vec<char>>();
// 当使用一个 usize 类型的值作为数组索引时,需要将它转换为整数类型,例如 i32,可以使用 as 关键字进行强制类型转换:
let mut res= cnt;
while r < blocks.len() {
print!("不会执行,因为迭代器只剩下(WBW)三个元素!故而此时向量长度为3,小于r");
cnt += if blocks[r] == 'W' {1} else { 0 };
cnt -= if blocks[l] == 'W' {1} else { 0 };
res = res.min(cnt);
l += 1;
r += 1;
}
// 而此时已经失去了原始blocks, 可知此种情况应该在一开始转为向量!除了遍历,别用什么迭代器。
res
}
pub fn minimum_recolors(blocks: &str, k: usize) -> usize {
let blocks_chars = blocks.chars().collect::<Vec<char>>();
let mut l = 0usize;
let mut r = 0usize;
let mut cnt = 0usize;
while r < k {
if blocks_chars[r] == 'W' {
cnt += 1;
}
r += 1;
}
let mut res = cnt;
while r < blocks_chars.len() {
if blocks_chars[r] == 'W' {
cnt += 1;
}
if blocks_chars[l] == 'W' {
cnt -= 1;
}
res = res.min(cnt);
l += 1;
r += 1;
}
res
}
```

@ -0,0 +1,242 @@
# 2383. 赢得比赛需要的最少训练时长
### description:
你正在参加一场比赛,给你两个 **正** 整数 `initialEnergy``initialExperience` 分别表示你的初始精力和初始经验。另给你两个下标从 **0** 开始的整数数组 `energy``experience`,长度均为 `n` 。你将会 **依次** 对上 `n` 个对手。第 `i` 个对手的精力和经验分别用 `energy[i]``experience[i]` 表示。当你对上对手时,需要在经验和精力上都 **严格** 超过对手才能击败他们,然后在可能的情况下继续对上下一个对手。击败第 `i` 个对手会使你的经验 **增加** `experience[i]`,但会将你的精力 **减少** `energy[i]` 。在开始比赛前,你可以训练几个小时。每训练一个小时,你可以选择将增加经验增加 1 **或者** 将精力增加 1 。返回击败全部 `n` 个对手需要训练的 **最少** 小时数目。
代码:
```rust
fn main() {
let (mut initial_energy, mut initial_experience) = (1, 1);
let energy:Vec<i32> = vec![1,1,1,1];
let experience:Vec<i32> = vec![1,1,1,50];
let res = min_number_of_hours(initial_energy, initial_experience, &energy, &experience);
println!("res is: {}", res);
let a:&Vec<i32> = &experience;
// &i 相当于 &i32, 故i为i32;
for &i in a{
if i < 2{
println!(" i < 2 is: {}", i + 2);
}else {
println!("i is: {}", i);
}
}
for i in a{
if *i < 2{
println!(" i < 2 is: {}", i + 2);
}else {
println!("i is: {}", i);
}
}
let a:&i32 = &5;
println!("{}", a + 3); // ok,会自动解引用
// println!("{}", a > 3); // error
}
fn min_number_of_hours(initial_energy:i32, mut initial_experience:i32, energy:&Vec<i32>, experience:&Vec<i32>) -> i32{
// let mut sum = 0;
// for i in energy{
// sum += i;
// }
// 与上面代码效果一样
let sum = energy.iter().sum();
let mut training_hours = if initial_energy > sum {0} else { sum - initial_energy + 1 };
for &i in experience{
if initial_experience > i{
initial_experience += i;
}else {
training_hours += i - initial_experience + 1;
initial_experience = 2 * i + 1;
}
}
// 如果是 experience:Vec<i32> 这样没问题,但experience:&Vec<i32>这样写得话,下面i需要*i来进行if,因为&i32在if时不会自动解引用。
// for i in experience{
// if initial_experience > *i{
// initial_experience += i;
// }else {
// training_hours += i - initial_experience + 1;
// initial_experience = 2 * i + 1;
// }
// }
training_hours
}
```
图:
![image-20230313141553496](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230313141553496.png)
修改函数参数,使用&[i32]替代Vec<i32>
```rust
fn main() {
let (mut initial_energy, mut initial_experience) = (1, 1);
let energy:Vec<i32> = vec![1,1,1,1];
let experience:Vec<i32> = vec![1,1,1,50];
let res = min_number_of_hours(initial_energy, initial_experience, &energy, &experience);
println!("res is: {}", res);
let a:&Vec<i32> = &experience;
for i in a{
if *i < 2{
println!(" i < 2 is: {}", i + 2);
}else {
println!("i > 2 is: {}", i);
}
}
// &i 相当于 &i32, 故i为i32;
for &i in a{
if i < 2{
println!(" i < 2 is: {}", i + 2);
}else {
println!("i > 2 is: {}", i);
}
}
let a:&i32 = &5;
println!("{}", a + 3); // ok,会自动解引用
// println!("{}", a > 3); // error
}
fn min_number_of_hours(initial_energy:i32, mut initial_experience:i32, energy:&[i32], experience:&[i32]) -> i32{
let sum = energy.iter().sum();
println!("sum : {}", sum);
let mut training_hours = if initial_energy > sum {0} else { sum - initial_energy + 1 };
for &i in experience{
if initial_experience > i{
initial_experience += i;
}else {
training_hours += i - initial_experience + 1;
initial_experience = 2 * i + 1;
}
}
println!("capital : {}", experience.len());
training_hours
}
fn min_number_of_hours2(initial_energy:i32, mut initial_experience:i32, energy:&Vec<i32>, experience:&Vec<i32>) -> i32{
// let mut sum = 0;
// for i in energy{
// sum += i;
// }
// 与上面代码效果一样
let sum = energy.iter().sum();
println!("sum : {}", sum);
let mut training_hours = if initial_energy > sum {0} else { sum - initial_energy + 1 };
for &i in experience{
if initial_experience > i{
initial_experience += i;
}else {
training_hours += i - initial_experience + 1;
initial_experience = 2 * i + 1;
}
}
// 如果是 experience:Vec<i32> 这样没问题,但experience:&Vec<i32>这样写得话,下面i需要*i来进行if,因为&i32在if时不会自动解引用。
// for i in experience{
// if initial_experience > *i{
// initial_experience += i;
// }else {
// training_hours += i - initial_experience + 1;
// initial_experience = 2 * i + 1;
// }
// }
println!("capital : {}", energy.capacity());
training_hours
}
```
区别:
1. 使用 `&[i32]` 更高效。`&Vec<i32>` 包含了指向堆上分配的数组的**指针**,以及两个整数表示数组的**长度和容量**,而 `&[i32]` 只包含一个指向堆上分配的数组的**指针**和一个整数表示数组的**长度**。因此,使用 `&[i32]` 要比使用 `&Vec<i32>` 更高效。
2. 使用 `&[i32]` 更灵活。`&[i32]` 不仅可以接受 `Vec<i32>` 的引用,还可以接受其他实现了 `AsRef<[i32]>` trait 的类型,包括数组和其他类型的 slice。这样,函数的参数类型就更加**通用**,更易于重用和组合。
3. 使用 `&Vec<i32>` 可以访问 `Vec<i32>` 的**容量**。在 `Vec<i32>` 中,容量是在堆上分配的数组的大小。使用 `&Vec<i32>` 作为函数参数,可以调用 `capacity()` 方法访问这个容量值。如果你的函数需要访问这个容量值,那么使用 `&Vec<i32>` 就更合适。
**总之,除非需要访问 `Vec<i32>` 的容量,否则应该优先使用 `&[i32]` 作为函数参数类型。**
![image-20230313144756297](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230313144756297.png)
c++:
```c++
#include <iostream>
#include <vector>
class Solution{
public:
int minNumberOfHours(int initialEnergy, int initialExperience, std::vector<int>& energy, std::vector<int>& experience) {
int sum = 0;
for (int e : energy) {
sum += e;
}
int trainingHours = initialEnergy > sum ? 0 : sum + 1 - initialEnergy;
for (int e : experience) {
if (initialExperience <= e) {
trainingHours += 1 + (e - initialExperience);
initialExperience = 2 * e + 1;
} else {
initialExperience += e;
}
}
return trainingHours;
}
int minNumberOfHours2(int initialEnergy, int initialExperience, std::vector<int>& energy, std::vector<int>& experience){
int sum = this->sum(energy);
int trainingHours = initialExperience > sum?0:sum-initialEnergy+1;
for(std::vector<int>::iterator it = experience.begin(); it != experience.end(); ++it){
if(initialExperience <= *it){
trainingHours += (*it - initialExperience)+1;
initialExperience = 2 * (*it) + 1;
} else{
initialExperience += *it;
}
}
return trainingHours;
}
private:
int sum(std::vector<int>& energy){
int sum = 0;
for (std::vector<int>::iterator it = energy.begin(); it != energy.end(); ++it)
{
sum += *it;
}
return sum;
}
};
int main() {
Solution *so = new Solution();
int initialEnergy = 1, initalExperience = 1;
std::vector<int> experience(4, 0);
// 两种赋值方式
std::vector<int> energy = {1, 1, 1, 1};
experience[0] = 1; experience[1] = 1; experience[2] = 1; experience[3] = 50;
int res = so->minNumberOfHours(initialEnergy, initalExperience, energy, experience);
std::cout << res << " ";
return 0;
}
```
图:
![image-20230313094107365](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230313094107365.png)

@ -0,0 +1,103 @@
# 2389. 和有限的最长子序列
### description:
给你一个长度为 `n` 的整数数组 `nums` ,和一个长度为 `m` 的整数数组 `queries` 。返回一个长度为 `m` 的数组 `answer` ,其中 `answer[i]``nums` 中 元素之和小于等于 `queries[i]`**子序列****最大** 长度 。
**子序列** 是由一个数组删除某些元素(也可以不删除)但不改变剩余元素顺序得到的一个数组。
代码:
```rust
fn main() {
let mut nums = vec![4, 5, 2, 1];
let queries = vec![3,10,21];
// let mut nums = vec![2, 3, 4, 5];
// let queries = vec![1];
let res = answer_queries(&mut nums, &queries);
for i in res {
println!(" {} ", i);
}
let queries = vec![0,1,3,7,12];
let idx = queries.binary_search_by(|&x| x.cmp(&6))
.map_or_else(|j| j, |j| j + 1);
println!("idx is {} ", idx-1);
let idx:i32 = queries.binary_search_by(|&x| x.cmp(&1))
.map_or_else(|j| j, |j| j + 1) as i32;
println!("idx is {} ", idx);
}
fn answer_queries(nums:&mut [i32], queries:&[i32]) -> Vec<i32>{
let n = nums.len();
let m = queries.len();
nums.sort_unstable();
let mut p = vec![0; n+1];
for i in 0..n {
p[i+1] = p[i] + nums[i];
}
let mut answer:Vec<i32> = Vec::with_capacity(m);
answer.resize(m, 0); // 使用push就不要使用resize了。
// let mut answer = vec![0; m]; // 2
for i in 0..m {
// let idx = match p.binary_search_by(|&x| x.cmp(&queries[i])) {
// Ok(j) => j+1,
// Err(j) => j,
// };
let idx = p.binary_search_by(|&x| x.cmp(&queries[i]))
.map_or_else(|j| j, |j| j + 1);
answer[i] = (idx - 1) as i32;
// answer.push((idx - 1) as i32); //resize了就不要用这个
}
answer
}
```
图:
![image-20230317141635077](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230317141635077.png)
![image-20230317143218173](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230317143218173.png)
`sort_unstable``sort` 方法都可以用于排序,区别在于它们的稳定性和性能。
- `sort_unstable` 不保证相等元素的相对顺序,但由于它不需要维护元素间的稳定性,因此通常比 `sort` 更快。
- `sort` 保证相等元素的相对顺序,它使用的是快排或归并排序等稳定排序算法。由于需要维护元素间的稳定性,因此通常比 `sort_unstable` 慢。
在实际使用中,如果对排序结果的稳定性没有特别要求,可以优先考虑使用 `sort_unstable` 来提高性能。如果需要保证相等元素的相对顺序,再使用 `sort`
1. **使用 `with_capacity` 方法预分配内存,避免反复分配和释放内存的开销。但是未初始化len为 0;**
`answer.resize(m, 0)` 的意思是将 `answer` 的长度调整为 `m`,并将多余的位置(如果有)设置为默认值 `0`。如果 `answer` 的长度已经大于等于 `m`,则不做任何操作。
2. **C++ upper_bound 与 rust binary_search_by 区别:**
upper_bound 的作用是找到 `p` 中第一个大于等于 `queries[i]` 的数的下标。`binary_search_by` 函数返回的是找到元素在数组中的下标,如果找到了就直接返回下标 `j`,否则返回不大于 `queries[i]` 的最大元素的下标 `j`。由于 `p` 数组中的元素是不递减的,所以如果要插入一个比最后一个元素还要大的元素,需要插入到 `n` 的位置,即数组的最后一个位置。因此,对于 `Err(j)` 的情况,插入的位置就是 `j`。而对于 `Ok(j)` 的情况,插入的位置应该是 `j+1`
在 C++ 中使用 `upper_bound` 函数来查找大于 `queries[i]` 的第一个元素的下标,这个函数返回的是一个迭代器,指向的是第一个大于 `queries[i]` 的元素,**所以需要再将其前移一位**,即 `upper_bound(f.begin(), f.end(), queries[i]) - f.begin() - 1`。而在 Rust 中使用 `binary_search_by` 函数来查找大于等于 `queries[i]` 的**第一个元素的下标**,这个函数返回的是一个 `Result<usize, usize>` 类型的值,表示查找的结果。如果查找成功,则返回查找到的元素在数组中的下标;如果查找失败,则返回一个在哪个位置插入元素可以保持数组的有序性的下标。由于 Rust 中数组的下标是从 0 开始的,所以需要将 `j` 加 1,即 `(j+1)-1`,得到插入的位置。
```rust
for i in 0..m {
let idx = p.binary_search_by(|&x| x.cmp(&queries[i]))
.map_or_else(|j| j, |j| j + 1);
answer[i] = (idx - 1) as i32;
}
```
这段代码的意思是在`p`数组中二分查找`queries[i]`,如果找到了,则返回该元素在数组中的索引`j`,并加一赋值给`idx`;如果没找到,则返回比`queries[i]`大的元素在数组中的索引`j`,并直接赋值给`idx`。最后将`(idx - 1)`转换成`i32`类型并赋值给`answer[i]`。
如果结果是`Ok`,则执行第一个闭包,即返回`Ok`中的值`j`;如果结果是`Err`,则执行第二个闭包,即返回`Err`中的值`j`。
<u>binary_search_by 返回的idx为插入(新增)到此位置不会影响函数顺序的下标,idx-1就是他可以替换的值得下标。</u>

@ -0,0 +1,122 @@
# 2488. 统计中位数为 K 的子数组
### description:
给你一个长度为 `n` 的数组 `nums` ,该数组由从 `1``n`**不同** 整数组成。另给你一个正整数 `k`。统计并返回 `nums` 中的 **中位数** 等于 `k` 的非空子数组的数目。
代码:
```rust
use std::collections::HashMap;
use std::cmp::Ordering;
fn main() {
let nums = vec![3, 2, 1, 4, 5];
let k = 4;
let nums = vec![2, 3, 1];
let k = 3;
let res = count_subarrays2(&nums, k);
println!(" {} ", res);
}
fn count_subarrays2(nums: &[i32], k:i32)->i32{
// 在 nums 数组中查找元素 k 的位置,使用了 iter() 方法和 position() 方法。
// |&x| 表示闭包接收一个参数,使用 x 来引用这个参数,而 & 表示参数是通过引用传递而不是值传递。| 符号用于表示闭包的开始和结束。
let mut k_index = nums.iter().position(|&x| x== k).unwrap();
let (mut ans, mut sum) = (0, 0);
let mut counts = HashMap::new();
counts.insert(0, 1);
// 使用&num是为了避免拷贝数据。(i32数据)不涉及所有权。。
for (i, &num) in nums.iter().enumerate(){
sum += sign2(num - k);
if i < k_index{
*counts.entry(sum).or_insert(0) += 1;
}else {
let prev_0 = *counts.get(&sum).unwrap_or(&0);
let prev_1 = *counts.get(&(sum-1)).unwrap_or(&0);
ans += prev_0 + prev_1;
}
}
for (i, num) in nums.iter().enumerate(){
println!("i is {} ; num is {}", i, num)
}
ans
}
fn sign2(num:i32)->i32{
match num.cmp(&0) {
Ordering::Equal => 0,
Ordering::Greater => 1,
Ordering::Less => -1,
}
}
fn count_subarrays(nums: &[i32], k:i32)->i32{
let n = nums.len();
let mut k_index:i32 = -1;
for i in 0..n {
if nums[i] == k{
k_index = i as i32;
break;
}
}
// println!(" index is {} ", k_index as usize);
let (mut ans, mut sum) = (0, 0);
let mut counts = HashMap::new();
counts.insert(0, 1);
for i in 0..n {
sum += sign(nums[i] - k);
if i < k_index as usize{
let count = counts.entry(sum).or_insert(0);
*count += 1;
}else {
let prev0 = *counts.get(&sum).unwrap_or(&0);
let prev1 = *counts.get(&(sum-1)).unwrap_or(&0);
ans += prev0 + prev1;
}
}
ans
}
fn sign(num:i32)->i32{
if num == 0{
0
}else {
if num > 0 { 1 } else { -1 }
}
}
// 在 Rust 中,数组的下标访问操作符 [] 已经被实现为语言特性,对于数组和切片类型,可以直接使用下标操作符访问其元素。''
// 在 Rust 中,数组或向量的元素可以通过索引访问,无论是使用 [] 运算符还是 get() 方法。
// 当使用 [] 运算符时,Rust 会自动解引用数组或向量的引用,并将其作为指向第一个元素的指针来处理。
// 因此,无论是 for road in &roads 还是 for road in roads,在使用 road[0] 访问数组或向量的第一个元素时,都将自动解引用指向该元素的指针,返回一个 i32 类型的值。
// 这是 Rust 语言的自动解引用功能的一部分,它使得代码更加简洁和易读。
```
**for (i, &num) in nums.iter().enumerate() 使用&num的好处:**
使用`&num`是为了**避免拷贝数据**。当使用迭代是的,这里只涉及空间占用。使用`&num`比`num`更好,因为在这里我们只是需要引用`num`的值,而不需要所有权。
使用引用可以减少副本的创建和内存占用。此外,当`nums`向量中的元素是较大的结构体或对象时,使用引用可能会更显著地减少内存使用和复制操作。器`nums.iter()`进行遍历时,每个元素都是被不可变地借用了,这意味着在遍历过程中不能修改这个元素。
**nums.iter().position(|&x| x== k).unwrap() 作用:**
`nums` 数组中查找元素 `k` 的位置,使用了 `iter()` 方法和 `position()` 方法。其中 `iter()` 方法将数组转换成迭代器,`position()` 方法会在迭代器上执行一个闭包函数,该闭包函数对每个元素进行检查,如果元素等于 `k`,就返回该元素在迭代器中的索引,否则返回 `None`。由于 `position()` 方法返回一个 `Option<usize>` 类型的值,因此还用了 `unwrap()` 方法来获取该值的实际值,如果获取失败则会触发 **panic**。最终返回的 `k_index` 就是元素 `k``nums` 数组中的位置。
|&x|这个是 Rust 闭包(closure)中的语法,用于表示闭包参数。`|&x|` 表示闭包接收一个参数,使用 `x` 来引用这个参数,而 `&` 表示参数是通过引用传递而不是值传递。`|` 符号用于表示闭包的开始和结束。在这个例子中,闭包用于在 `nums` 数组中查找值等于 `k` 的元素的下标位置。
![image-20230316110236075](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230316110236075.png)

@ -0,0 +1,79 @@
# 3
## Functions
函数的关键字为fn,main函数是所有程序的入口。rust中变量名与函数名都使用蛇形方式来定义:所有字母均小写,并用下划线分隔单词。
![image-20221230094854809](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221230094854809.png)
### 带参数的函数
函数签名中必须声明每个参数类型。多个参数之间用逗号隔开。
![image-20221230095815943](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221230095815943.png)
### 语句和表达式
语句是执行某些操作但没有返回值的指令。
表达式是计算结果值。
![image-20221230100640655](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221230100640655.png)
无法做到c语言中的 x=y=6;
表达式可以是语句的一部分:计算结果是表达式,调用函数是表达式,调用宏是表达式,用大括号创建的新作用域块是一个表达式。
同大多数行不同,该x+1行末尾没有分号。表达式不包括结束分号,若在表达式末尾添加分号,就成为了一个语句,就不再返回值。
![image-20221230105445787](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221230105445787.png)
### 带返回值的函数
Rust中,函数的返回值和函数体块中最终表达式的值同义。返回值需通过箭头(->)声明它们的类型。可以通过return关键字从函数中提前返回,但更多的函数会隐式返回最后一个表达式。
![image-20221230110750052](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221230110750052.png)
## Control Flow
### if Expressions
if 的条件必须是一个bool值,
![image-20221230111849826](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221230111849826.png)
#### if在let声明中使用(类似 int x = condition? 1:2)
因为变量必须有单一类型,而 Rust 需要在编译时明确地知道 number变量是什么类型。故if和else表达式结果需为同一类型。
![image-20221230112429207](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221230112429207.png)
### loops
rust循环有三种:loop、while和for。
#### loop
![image-20221230113949310](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221230113949310.png)
循环标签必须以单引号开头。多级嵌套循环时,当在内循环中使用外循环的continue和break时。可以通过给外循环指定一个循环标签来实现。
![image-20221230134828392](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221230134828392.png)
#### while
条件循环。节省了loop中各种的if,else以及break代码。当条件为true时执行循环。
![image-20221230135710864](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221230135710864.png)
for
rust中最常用的循环结构。使用for遍历数组时,不需考虑数组的个数。减少了出现错误的可能性。
![image-20221230140057284](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221230140057284.png)
![image-20221230140833380](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221230140833380.png)

@ -0,0 +1,204 @@
# 3
## Variables and Mutability
默认情况下,**变量是不可变的**。在没有使用mut的情况下,若重复修改变量的值会产生error[E0384]: cannot assign twice to immutable variable `x`
### Constants
同不可能变量相同,常量的值也不可修改。但是常量不允许使用mut,常量不仅在默认情况下是不可变的--**它们始终是不可变的的**。定义常量使用const关键字,而且必须在初始化时赋值,值只能是一个常量表达式。
```rust
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
```
**Rust 的常量命名约定是全部使用大写字母,单词之间有下划线**
### Shadowing
声明一个同名新变量,覆盖先前的变量。
![image-20221228144724096](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221228144724096.png)
![image-20221228144756450](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221228144756450.png)
shadowing和使用mut存在两点不同,使用mut是不会改变变量类型,而使用let实际上是创建了一个同名新变量。且此变量可以不为之前的类型。
如下第一个spaces变量是字符串类型,第二个spaces变量是数字类型。使用let可以减少重新起名。但用mut无法做到这种事。
```
let spaces = " ";
let spaces = spaces.len();
```
运行时错误:mismatched types
```
let mut spaces = " ";
spaces = spaces.len();
```
## Data Types
rust 中每个值都有特定的数据类型。scalar and compound。
rust 是一种***静态类型***语言,因此它必须在编译时知道所有变量的类型。编译器通常会根据值和使用它的方式来推断值的类型。但在存在多种类型的情况下,需指明具体类型。例如数字类型。
```
let guess: u32 = "42".parse().expect("Not a number!");
```
### scalar
Scalar 用于表示单个值。rust中主要有四种类型:integers,floating-point numbers, Booleans, 以及characters。
#### integers
整数是没有小数部分的数字。i开头的为有符号的整数,u开头的为无符号的整数。
| Length | Signed | Unsigned |
| :------: | :----: | :------: |
| 8-bits | i8 | u8 |
| 16-bits | i16 | u16 |
| 32-bits | i32 | u32 |
| 64-bits | i64 | u64 |
| 128-bits | i128 | u128 |
| arch | isize | usize |
当需要表示有符号数的时候,需使用Signed,有符号数使用二进制补码来存储(原码取反+1)。二者的取值范围如下:
![image-20221228164640003](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221228164640003.png)
| 进制 | 示例 |
| :--------------: | :---------: |
| Decimal | 98_222 |
| Hex | 0xff |
| Octal | 0o77 |
| Binary | 0b1111_0000 |
| Byte (`u8` only) | b'A' |
![image-20221228170044008](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221228170044008.png)
注:数字间可以使用**_**为视觉分隔符。1_000的值等同于1000。
![image-20221229085022060](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221229085022060.png)
##### overflow
![image-20221229090856434](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221229090856434.png)
当使用u8类型时,值的范围为0-255,若将值赋为256,不管release还是debug都会报错 literal out of range for `u8 `
![image-20221229100932401](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221229100932401.png)
但是要是换成此种写法,在debug mode下会产生不变,而在 release mode下,不会检查整数溢出,若发生溢出,Rust执行二进制补码进行包装,当值为u8类型时,256会变成0,257会变成1。程序不会panic,但是值可能不是期望值。
处理显式溢出,可以使用标准库的以下方法 [rust doc](https://doc.rust-lang.org/std/primitive.i32.html#method.overflowing_add):
1. wrapping_add 当overflow时,会自动回到最小值,结果和使用release相同,但是使用Wrapping包装类可以在debug模式下使用,同时避免出现不可预期的结果。
![image-20221229103713797](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221229103713797.png)
2. 使用checked_add方法。会在溢出时返回None,方便定位是否产生溢出。
![image-20221229111802596](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221229111802596.png)
3,使用overflowing_add。此方法会返回一个结果和一个布尔值来定位是否溢出,相较于wrapping_add多一个是否溢出的标记。
![image-20221229112810102](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221229112810102.png)
4. 使用saturating_add。越界时数值为临界点的值。
![image-20221229113501276](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221229113501276.png)
#### Floating-Point Types
浮点数是带小数点的数字。Rust有f32和f64两种。浮点数都是有符号数,且默认为f64。
```
fn main() {
let x = 2.0; // f64
let y: f32 = 3.0; // f32
}
```
#### Numeric Operations
rust数字类型支持所有基本数学运算,整数除法时,结果取最接近0的整数。
![image-20221229160910795](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221229160910795.png)
#### The Boolean Type
布尔类型一个字节,有true和false两种值。可以隐式和显式声明。
```
fn main() {
let t = true;
let f: bool = false; // with explicit type annotation
}
```
#### The Character Type
char是rust最原始的类型。 字符型使用单引号指定文字,而非双引号。同时大小为4个字节,表示一个Unicode标量值,故而字符型不仅可以表示ASCII,也可以表示带音调的字母、中文、日文、韩文字符、emoji以及零宽空格。
Unicode标量值的范围 U+0000 到 U+D7FF 和 U+E000 到 U+10FFFF(含)。字符于rust中比较特殊。
![image-20221229162843669](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221229162843669.png)
### compound Types
复合类型可以将多个值组合成一个类型。rust中有两种基本的符合类型:元组(tuples)和数组(arrays)。
#### The Tuple Type
元组是将多个具有多种类型的值组合成一个复合类型的通用方法。固定长度:声明后,它们的大小就不能增加或缩小。若要从元组中取单个值:1.通过模式匹配来解析元组值。 2.通过使用句点(.)后接索引来访问元素(从0开始)。
![image-20221229170443559](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221229170443559.png)
当一个元组没有任何值,那么它拥有一个特殊的名字-> unit。 这个值及其对应的类型都写作(),表示空值或空返回类型。一般用于函数无返回值时。
```rust
fn say_hi() -> () {
println!("Hi!");
}
// 显式返回
fn say_hi() -> () {
println!("Hi!");
return ();
}
```
#### The Array Type
数组每个元素必须具有相同的类型,Rust中数组具有固定长度。数组中的值在方括号内以逗号分隔。故而当数量固定时数组很有用。vector相较于数组更灵活,可以扩大或缩小大小。
![image-20221229173612760](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221229173612760.png)
![image-20221229173829051](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221229173829051.png)

@ -0,0 +1,108 @@
1. 华氏度和摄氏度互相转化
```
use std::io;
fn main() {
println!("请输入您的体温: ");
let mut temp = String::new();
io::stdin()
.read_line(&mut temp)
.expect("输入数据有误");
let temp: f64 = temp.trim().parse().expect("类型转换失败");
println!("请输入温度单位(F或C):(若无特殊设置,默认大于50度为华氏度):");
let mut unit = String::new();
io::stdin()
.read_line(&mut unit)
.expect("输入数据有误");
// 也可以通过去字符串,我直接true false了。
let unit2:bool = match unit.trim(){
"F"=>true,
"C"=>false,
_=>{
if temp > 50.0 {
println!("使用华氏温度单位(F)");
true
}else {
println!("使用摄氏温度单位(C)");
false
}
}
};
println!("FFF is: {}", temp);
println!("type is: {}", unit2);
let res = trans(temp, unit2);
println!("res is: {}", res);
}
fn trans(temp:f64, is_fahrenheit:bool)->f64{
if is_fahrenheit{
(temp-32.0)*5.0/9.0
}else {
temp*9.0/5.0 +32.0
}
}
```
2. 生成第*n*个斐波那契数
```
use std::io;
fn main() {
println!("请输入你要查的队列下标");
let mut n = String::new();
io::stdin()
.read_line(&mut n)
.expect("数据输入有误");
let n:i32 = n.trim().parse().expect("类型转换失败");
let mut a = 0;
let mut b = 1;
for i in 0..n{
let c = a+b;
a = b;
b = c;
}
println!("res is: {}", a);
}
```
3. 圣诞节的十二天
```
fn main() {
let days = ["first", "second", "third", "fourth", "fifth", "sixth",
"seventh", "eighth", "ninth", "tenth", "eleventh", "twelfth"];
let gifts = ["A partridge in a pear tree.", "Two turtle doves,",
"Three French hens,", "Four calling birds,",
"Five golden rings,", "Six geese a-laying,",
"Seven swans a-swimming,", "Eight maids a-milking,",
"Nine ladies dancing,", "Ten lords a-leaping,",
"Eleven pipers piping,", "Twelve drummers drumming,"];
for i in 0..12{
println!("On the {} day of Christmas, \nmy true love sent to me", days[i]);
for j in (0..=i).rev(){
println!("{}",gifts[j]);
}
println!();
}
println!("Hello, world!");
}
```

@ -0,0 +1,90 @@
# References and Borrowing
传递引用可以使其他变量拥有数据,而不获得值的所有权。引用类似于指针,根据地址来访问值,引用保证了在该引用的生命周期内指向特定类型的有效值。
![image-20230103231056010](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230103231056010.png)
引用图示: (与使用引用相反的`&`是*取消引用*,这是通过取消引用运算符完成的`*`)
![image-20230103231643899](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230103231643899.png)
```rust
fn calculate_length(s: &String) -> usize { // s is a reference to a String
s.len()
} // Here, s goes out of scope. But because it does not have ownership of what
// it refers to, it is not dropped.
```
变量`s`有效的范围与任何函数参数的范围相同,但引用指向的值在`s`停止使用时不会被删除,因为`s`没有所有权。当函数将引用作为参数而不是实际值时,我们不需要返回值来归还所有权,因为我们从来没有所有权。 **usize表示无符号整数、值的范围取决于系统架构(32位或64位)**
正如默认情况下变量是不可变的一样,引用也是如此,未加mut就不可改变。
![image-20230103232633786](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230103232633786.png)
## 可变引用
![image-20230103232850116](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230103232850116.png)
一个值的可变引用同一时间只能有一个(必须等第一个借用者使用完才行,类似于单个可变引用)。
![image-20230103233103843](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230103233103843.png)
因为我们不能在同一时间多次可变借用 s。第一个可变借用在 r1 中,必须持续到在 println! 中使用它之后,但在创建那个可变引用并使用它之间,我们尝试创建另一个可变引用 r2,该引用借用了与 r1 相同的数据。
![image-20230104092307368](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230104092307368.png)
类似于单个可变引用:
![image-20230104092455048](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230104092455048.png)
限制防止同时对相同数据进行多次可变引用,允许在非常受控的方式下进行mut。这是新 Rustaceans 难以应对的,因为大多数语言都允许您在需要时进行mut。这种限制的好处在于,Rust 可以在编译时防止数据竞争。数据竞争类似于竞争条件,当出现以下三种行为时会发生:
1. 两个或更多指针同时访问相同的数据。
2. 至少有一个指针用于写入数据。
3. 没有同步访问数据的机制。
数据竞争导致未定义行为,在运行时跟踪它们时很难诊断和修复;Rust 通过拒绝编译具有数据竞争的代码来防止这个问题!
### 多个可变引用(不同时)
新增范围,类似于上面的代码,但是更直观。
![image-20230104095047747](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230104095047747.png)
### 可变引用与不可变引用
可变引用允许在引用的数据上修改数据,而不可变引用则不允许。
在 Rust 中,如果要修改一个变量,则需要使用可变引用。而如果只是希望读取一个变量的值,而不希望修改它,则可以使用不可变引用。
![image-20230104101314651](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230104101314651.png)
类似与上面,在最后一次使用之后(范围不重叠),运行使用,建议加括号。**编译器可以判断在范围结束之前的某个点不再使用该引用。**
![image-20230104101620161](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230104101620161.png)
加上括号的版本(更直观)!
![image-20230104101738544](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230104101738544.png)
## 悬挂引用
在有指针的语言中,很容易通过在保留指向内存的指针的同时释放某些内存来错误地创建一个悬挂指针(即指向可能已给其他人的内存位置的指针)。相比之下,**Rust 可以保证引用永远不会是悬挂引用:如果你对某些数据有一个引用,编译器会确保数据在引用数据之前不会离开作用域。**
引用没有所有权,一下案例赶回的是借用,而真正的所有权仍在s上,而s在离开作用域后就已经被丢弃。因此以下代码是无效的!
![image-20230104105242918](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230104105242918.png)
正确的方法是直接移动所有权!
![image-20230104105736786](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230104105736786.png)

@ -0,0 +1,220 @@
# Slice Type
切片可以引用集合中连续的元素序列,而不是整个集合。**切片是一种引用,因此他没有所有权。**
## 不使用切片找单词
下面的方法可以获取到第一个单词结尾的下标,但此值是独立于String的,所以不能保证他将来有效。
![image-20230104135420678](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230104135420678.png)
将String转为字节数组。
```rust
let bytes = s.as_bytes();
```
使用迭代器遍历字节数组,enumerate包装了结果iter并将每个元素作为元组的一部分返回。第一个元素为索引,第二个元素为该元素的引用。
```rust
for (i, &item) in bytes.iter().enumerate(){}
```
### 获取结果后修改字符串
```rust
fn first_word(s: &String) -> usize {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return i;
}
}
s.len()
}
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s); // word will get the value 5
s.clear(); // this empties the String, making it equal to ""
// word still has the value 5 here, but there's no more string that
// we could meaningfully use the value 5 with. word is now totally invalid!
}
```
此种情况下,若想通过获取的下标值配合字符串获取第一个单词便会出错。
![image-20230104145801151](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230104145801151.png)
## String Slices
字符串*切片*是对字符串一部分的引用。
![image-20230104155425732](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230104155425732.png)
![image-20230104155706354](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230104155706354.png)
一些切片的简便写法:
1. 如果你想从索引 0 开始,你可以删除两个句点之前的值。
2. 如果您的切片包含 的最后一个字节`String`,您可以删除尾随数字。
3. 可以删除这两个值以获取整个字符串的一部分。
```rust
let s = String::from("hello");
let len = s.len();
// 以下相等
let slice = &s[0..2];
let slice = &s[..2];
// 以下相等
let slice = &s[3..len];
let slice = &s[3..];
// 以下相等
let slice = &s[0..len];
let slice = &s[..];
let slice = &s; // 当参数为&str时,二者相同;当参数为&String时,只能传&s
```
### 修改后的first_word
![image-20230105091042697](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230105091042697.png)
![image-20230105091209645](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230105091209645.png)
![image-20230105091306505](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230105091306505.png)
**借用规则,如果我们有一个对某物的不可变引用,我们就不能同时使用一个可变引用**。我认为就是被借用后,未使用完就不可以修改。
### String Slices as Parameters
```rust
fn first_word(s: &String) -> &str {
fn first_word(s: &str) -> &str {
第一个函数的参数是一个 String 类型的引用,返回值是一个字符串 slice。第二个函数的参数是一个字符串 slice,返回值也是一个字符串 slice。
这两个函数在功能上是类似的,但它们处理的数据类型不同。第一个函数传入的是一个 String 类型的值,而第二个函数传入的是一个字符串 slice。
```
在需要字符串为参数时,更好的选择是使用**字符串切片**为参数,它允许我们对`&String`值和`&str`值使用相同的函数。
![image-20230105094853847](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230105094853847.png)
传切片
![image-20230105094954607](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230105094954607.png)
```rust
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
fn main() {
let my_string = String::from("hello world");
// `first_word` works on slices of `String`s, whether partial or whole
let word = first_word(&my_string[0..6]);
let word = first_word(&my_string[..]);
// `first_word` also works on references to `String`s, which are equivalent
// to whole slices of `String`s
let word = first_word(&my_string);
let my_string_literal = "hello world";
// `first_word` works on slices of string literals, whether partial or whole
let word = first_word(&my_string_literal[0..6]);
let word = first_word(&my_string_literal[..]);
// Because string literals *are* string slices already,
// this works too, without the slice syntax!
let word = first_word(my_string_literal);
}
```
![image-20230105105152810](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230105105152810.png)
在let s = "hello world"时,创建的是字符串字面量。使用 `from` 函数创建的字符串和字符串字面量是不同的东西。前者是**堆**上的字符串,后者是**常量**存储在程序二进制文件中的字符串。如果你将一个 `&` 引用传递给字符串字面量,它会被解释为字符串 slice。但是,如果你将 `&` 引用传递给通过 `from` 函数创建的字符串,它将会被解释为**字符串类型的引用**。
这是因为字符串字面量本身就是字符串 slice,所以也可以直接调用 first_word 函数,无需使用 slice 语法。
如果你传入了一个字符串字面量的引用,比如 &"hello world",那么它将会被转化为一个字符串 slice 类型,即 &str。所以,传入 &"hello world" 与传入 "hello world" 是等价的。
#### 使用from创建的字符串与字符串字面量
使用 `String::from` 创建的字符串是在堆上的动态内存。使用 `&` 获取这个字符串的引用,返回的是一个字符串 slice,而不是字符串字面量。
字符串字面量是编译时就被确定的**常量**字符串。使用 `let s = "str"` 定义一个字符串字面量,这个字符串存储在程序的**只读数据段**中,可以直接使用。使用 `&` 取出来的依然是字符串字面量。字符串字面量是slice的一种特殊情况。其值是字符串字面量的地址(只读内存区域)。
## Other Slices
字符串切片是特定与字符串的。但其他集合也可以使用切片。
![image-20230105111754222](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230105111754222.png)
`{:?}` 是格式化输出中的一种控制符,用于输出一个值的“调试形式”。 在本例中,使用 `{:?}` 会输出 `slice` 值的调试形式,这意味着在输出的序列中会包含所有元素的值,以及切片的边界。"{:#?}"是"Debug"格式化输出中的一个选项。它输出更好看的、更具可读性的调试输出。这种输出格式适用于较为复杂的数据结构。"{:?}"是"Debug"格式化输出中的另一个选项。它输出的内容与"{:#?}"的输出类似,但是在行与行之间没有缩进。由于"{:?}"没有缩进,输出的信息可能不是那么好看,但是也更加紧凑。所以,"{:#?}"和"{:?}"的主要区别在于输出的可读性和紧凑性之间的权衡。
控制符包括但不限于:
- `{}`:按照默认方式输出值。
- `{:b}`:以二进制输出值。
- `{:o}`:以八进制输出值。
- `{:x}`:以十六进制输出值。
- `{:e}`:以科学计数法输出值。
在 Rust 中,数组的数据可以存储在堆或者栈上,具体取决于数组的大小。如果数组的大小是固定的,那么数组的数据就会存储在栈上;如果数组的大小是可变的,那么数组的数据就会存储在堆上。
```rust
// 栈上
fn main() {
let a = [1, 2, 3, 4, 5];
let b: [i32; 5] = [1, 2, 3, 4, 5];
}
// 堆上
fn main() {
let c = vec![1, 2, 3, 4, 5];
}
```

@ -0,0 +1,145 @@
# 4 Ownership
所有权是Rust最独特的特征。使rust能在不需要垃圾收集器的情况下,保证内存安全。所有权的相关特征:借用、切片以及rust如何在内存中布局数据。
## 什么是所有权?
所有权是rust管理内存的一组规则。所有语言都必须在运行时管理计算机内存。java中使用的是垃圾回收机制,c++使用显式分配和释放内存,而rust是通过所有权定义一组规则,若违法任何规则,则程序无法变异。所有权的任何功能都不会在程序运行时减慢程序的速度。
堆栈:先进后出,分配更快,因为始终在堆栈的顶部分配。
堆:访问速度比堆栈慢,因为需根据指针。
### 所有权规则:
1. 每个值都有一个owner。
1. 同一时间只能有一个owner。
1. 当owner超出范围时,改值将被删除。
### 变量及其有效范围:
1. 当s进入范围时,他是有效的。
2. 在超出范围之前一直有效。
### String 所有权
![image-20230103115454713](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230103115454713.png)
![image-20230103140613746](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230103140613746.png)
字符串文字(不带mut)是不可变的,但使用push_str,会改变字符串。
### 内存和分配
当string不带mut时,在编译时就知道内容,故而文本直接被硬编码到可执行文件中,速度高效。
但大多情况下,String类型都是可变的、可增长的文本片段。为支持可变需做以下工作:
1. 必须在运行时从内存分配器请求内存。
2. 当字符串定义完成后,需使用一种方法将内存返回给分配器。
第一部分由我们完成:当调用String::from时,它的实现请求它需要的内存。
第二部分略有不同。在java中,gc会跟踪并清理不再使用的内存。在c++中,开发人员需确定何时不再使用内存,并调用代码显式释放内存(困难重重)。而在rust中,采用不同的方式:一个变量超出范围,内存就会返回。
{
let s = String::from("hello"); // s is valid from this point forward
// do stuff with s
} // this scope is now over, and s is no
// longer valid
当调用String::from时,我们将String所需的内存返回给分配器。当字符串变量离开其作用域时,rust会自动调用drop函数。创建String类型的开发可以在drop函数中编写代码,将内存返还给分配器、Rust在关闭大括号时自动调用drop函数。
### 变量交互
简单类型交互
![image-20230103150405427](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230103150405427.png)
### String类型交互
String类型的三部分:指向保存字符串堆的指针,长度和容量。
![image-20230103155545065](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230103155545065.png)
![image-20230103155938348](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230103155938348.png)
![image-20230103154738909](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230103154738909.png)
![image-20230103154600735](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230103154600735.png)
若不转移所有权,当一个变量超出范围时,rust在清理堆内存时,将同时释放s1和s2相同位置的内存。将会产生双重释放错误,会导致内存损坏,也会导致安全漏洞。
不同于浅拷贝,在rust中此操作会使第一个变量无效,故它被称为移动。此操作中我们会说s1被移动到了s2。
![image-20230103160852981](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230103160852981.png)
如此如此,就只有s2有效,当超出范围时只有它单独释放内存!
此现象也说明了一个设计选择: **Rust永远不会自动创建数据的“深”副本。因此,可以认为任何自动复制在运行时性能方面都是廉价的。**
### clone
![image-20230103163926151](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230103163926151.png)
![image-20230103164637401](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230103164637401.png)
同时复制了堆数据,开销很大。
### 只在堆栈中的数据
在编译时具有已知大小的整数等类型完全存储在堆栈中,因此可以快速复制实际值。这意味着我们没有理由`x`在创建变量后阻止其生效`y`。换句话说,这里的深拷贝和浅拷贝没有区别,所以调用`clone`和通常的浅拷贝没有什么不同,
```rust
let x = 5;
let y = x;
println!("x = {}, y = {}", x, y);
```
Rust 中有一种特殊的注释,叫做 `Copy` trait,它可以放在存储在栈中的类型上。如果一个类型实现了 `Copy` trait,使用它的变量不会移动,而是被简单复制,使它们在赋值给另一个变量后仍然有效。
如果类型或其任何部分实现了 `Drop` trait,Rust 将不会让我们在类型上添加 `Copy` 注释。如果类型需要在值超出作用域时执行特殊操作,并且将 `Copy` 注释添加到该类型,则会出现编译时错误。
作为一般规则,任何简单标量值组都可以实现 `Copy`,而任何需要分配或为某种资源的类型都不能实现 `Copy`。以下是一些实现 `Copy` trait 的类型:
- 所有整数类型,例如 u32。
- 布尔类型 bool,其值为 true 和 false。
- 所有浮点类型
- 所有字符类型 char。
- 元组,如果它们只包含也实现了 `Copy` 的类型。例如,(i32,i32)实现了 `Copy`,但(i32,String)不能。
这是实现 `Copy` trait 的一些类型的完整列表。注意,这些类型在赋值给另一个变量时不会移动,因为它们实现了 `Copy` trait,而不是因为它们存储在栈中。
例如,像数组这样的类型可以存储在堆上,但也可以实现 `Copy` trait。这意味着在赋值给另一个变量时,该数组的所有内容将被简单复制,而不是移动。
同样,字符串 slice 也可以实现 `Copy` trait,尽管它们存储在堆上。这意味着在赋值给另一个变量时,它们将被简单复制,而不是移动。
总的来说,如果希望类型能够被简单复制而不是移动,可以尝试将 `Copy` trait 添加到该类型上。这在某些情况下可以使代码更简单,并且在运行时间方面也很容易。但是,要注意,如果类型需要在值超出作用域时执行特殊操作,则不能将 `Copy` trait 添加到该类型。
### 函数所有权
rust中变了传递给函数的机制与将变量赋值给另一变量类似。
![image-20230103171551681](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230103171551681.png)
String类型不实现`Copy` trait,故而函数结束后打印s(使用&s就没有此问题)会出错,但x由于是int类型,故函数结束后仍然存在。
### Return Values and Scope
返回值也可以转移所有权。
![image-20230103173822709](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230103173822709.png)
变量的所有权每次都遵循相同的模式:将一个值赋给另一个变量会移动它。当包含堆上数据的变量超出范围时,drop除非数据的所有权已移至另一个变量,否则将清除该值。
![image-20230103174018518](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230103174018518.png)
但对于一个本应通用的概念来说,这是太多的仪式和大量的工作。对我们来说幸运的是,Rust 有一个使用值而不转移所有权的特性,称为*引用*。

@ -0,0 +1,78 @@
# 使用struct的场景
## 不使用结构
计算由单独的宽度和高度变量指定的矩形的面积,相关性较差。将宽度和高度组合在一起会更具可读性和更易于管理
![image-20230105164036047](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230105164036047.png)
## 用元组进行重构
我们现在只传递一个参数。但是现在对具体的width和height没有明确的命名。可能会使人混淆。
![image-20230105165206949](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230105165206949.png)
## 使用结构重构(增加更多信息)
传递一个结构体引用(要是不想要owner了,传整体也行)给函数。我们想要借用结构而不是拥有它。这样,`main`保留其所有权并可以继续使用`rect1`,这就是我们`&`在函数签名和调用函数的地方使用的原因。
![image-20230105170634770](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230105170634770.png)
## 使用派生特征添加有用的功能
我们看到的基本类型`Display`都是默认实现的,因为只有一种方式可以向`1`用户显示一个或任何其他基本类型。但是对于结构, `println!`格式化输出的方式就不那么清晰了,因为有更多的显示可能性。
![image-20230105234833237](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230105234833237.png)
以调试模式打印,但我们必须明确选择让该功能可用于我们的结构。
![image-20230105235153536](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230105235153536.png)
修改后:
![image-20230105235736674](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230105235736674.png)
加强版的输出 {:#?}
![image-20230105235843271](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230105235843271.png)
## trait
在 Rust 中,trait 是用来描述类型的一组特征的。trait 可以被用来定义共同的行为,并被多个类型实现。
一些常见的 trait 包括:
- `Clone`: 这个 trait 允许我们复制值,而不是移动它们。
- `Copy`: 这个 trait 允许我们在移动或覆盖值的同时保留一个副本。
- `Debug`: 这个 trait 允许我们用 `{:?}` 格式化调试输出。
- `Eq`: 这个 trait 允许我们比较两个值是否相等。
- `Ord`: 这个 trait 允许我们比较两个值的大小。
- `PartialEq`: 这个 trait 允许我们比较两个值是否相等,但是类型不必完全匹配。
- `PartialOrd`: 这个 trait 允许我们比较两个值的大小,但是类型不必完全匹配。
- `Hash`: 这个 trait 允许我们使用值作为键值。
这些 trait 都是 Rust 标准库中内置的。你也可以自己定义新的 trait。
## dbg!宏打印
并且它不需要取该表达式的引用(反而是将表达式的所有权交还给它)。它会打印出该dbg!宏调用在代码中的文件名和行号以及该表达式的值,然后**返回该值的所有权**。
调用 dbg! 宏会将信息输出到标准错误控制台流 (stderr),而 println! 则会输出到标准输出控制台流 (stdout)。这意味着,使用 dbg! 宏输出的信息会出现在终端的错误信息中,而使用 println! 输出的信息则出现在终端的正常输出中。
简单案例:
![image-20230106000951815](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230106000951815.png)
本案例:
![image-20230106001535351](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230106001535351.png)

@ -0,0 +1,67 @@
# Method Syntax
方法与函数类似:我们使用fn关键字和名称来声明它们,它们可以有参数和返回值,并且在从其他地方调用方法时会运行一些代码。与**函数**(独立的代码块,它可以在任何地方被调用)不同的是,**方法**在结构体的上下文中定义,它们的第一个参数总是**self**,表示调用方法的结构体的实例。
![image-20230106092052958](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230106092052958.png)
为了在 Rectangle 的上下文中定义函数,我们为 Rectangle 开始一个 **impl**(实现)块。此 impl 块中的所有内容都将与 **Rectangle** 类型相关联。然后,将 area 函数移动到 impl 大括号内,并在签名和函数体内的所有位置将第一个(在这种情况下,仅有)参数更改为 **self**
&self`实际上是`self: &Self。
**方法的第一个参数必须命名为 self。 self 的类型必须是 Self**。写&self而不是self(可以写),原因与在函数版本中使用 &Rectangle 相同:**我们不想拥有所有权**,只想读取结构体中的数据,而不是写入它。如果我们想在方法执行时更改调用方法的实例,则应使用 &mut self 作为第一个参数。拥有 self 的方法很少使用;**通常使用此技术是因为方法将 self 转换为其他内容,并且您希望防止调用者在转换后使用原始实例**。
使用方法而不是函数的主要原因,除了提供方法语法并且无需在每个方法的签名中重复 self 类型之外,还是为了组织。**我们将所有可以使用类型实例的内容放在一个 impl 块中**。
## getters
方法名可以与字段名相同,当我们提供与字段同名的方法时,我们希望它只返回字段中的**值**而不做任何其他事情。像这样的方法称为***getters***,Rust 不会像其他一些语言那样自动为结构字段实现它们。Getters 很有用,因为您可以将**字段设为私有**,但将方法设为公开,从而作为类型的公共 API 的一部分启用对该字段的只读访问:
![image-20230106094910062](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230106094910062.png)
## -> Operator
Rust 没有等价于 -> 运算符的东西;相反,Rust 有一个称为**自动引用**和**解除引用**的功能。调用方法是 Rust 中拥有此行为的少数地方之一。**仅适用于方法的接收者,意思就是如果方法参数接的是&self,你直接穿rect也可以,会隐式转为&rect。有了这个特征,传方法直接无脑传就行。接收者来决定穿的具体模式**
这是它的工作方式:当使用 object.something() 调用方法时,Rust 会自动添加 &&mut 或 *,使 object 与**方法的签名匹配**。换句话说,以下内容是相同的:
p1.distance(&p2); (&p1).distance(&p2); 第一个看起来要干净得多。这种自动引用行为之所以能够工作,是因为方法具有明确的**接收者**——**self** 的类型。 给定方法的接收者和名称,Rust 可以明确地确定方法是读取(&self)、修改(&mut self)还是消耗(self)。 Rust 将借用隐式应用于方法接收者的事实是使所有权在实践中更加容易。
图1
![image-20230106100950035](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230106100950035.png)
图2
![image-20230106101543144](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230106101543144.png)
图3
![image-20230106102034323](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230106102034323.png)
## 更多参数的方法
![image-20230106102924223](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230106102924223.png)
## 相关函数
在 impl 块内定义的所有函数都被称为关联函数,因为它们与 impl 后面的类型相关联。我们可以定义没有 self 作为第一个参数的关联函数(因此不是方法),因为它们不需要类型的实例才能工作。我们已经使用了一个类似的函数:String::from 函数,它定义在 String 类型上。
**非方法的关联函数**通常用作**构造函数**,它们将返回 struct 的新实例。这些函数通常被称为 new,但 new 不是一个特殊的名称,也不是内置在语言中的。
![image-20230106113123490](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230106113123490.png)
## 多个impl
首先,可以使用多个 impl 块来定义不同的行为,其中每个 impl 块定义一组功能。这可以使类型的实现更加清晰,并使代码更容易阅读和维护。
其次,可以使用多个 impl 块来分离相关的功能,并使用不同的 impl 块来实现某个 trait 的不同版本。这可以让类型具有更多的灵活性,并允许更好地适应变化。
![image-20230106114608539](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230106114608539.png)

@ -0,0 +1,67 @@
# struct
类似于java中的类。关键字为struct。
![image-20230105114220159](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230105114220159.png)
这个user1没设置mut, 故实例不可以改。
![image-20230105114604701](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230105114604701.png)
注意,整个实例必须是可变的;**Rust 不允许我们仅将某些字段标记为可变的**。如果你在创建结构体的时候使用了 mut,那么整个结构体都是可变的,所有的字段都可以被修改。
## 根据特定自动返回user
类似自己写了个构造函数。 ![image-20230105141004996](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230105141004996.png)
## Using the Field Init Shorthand
将函数参数命名为与结构字段同名是有意义的,但必须重复字段名称`email`和`username`变量有点乏味。因为参数名和结构字段名在清单,故可以使用field init 速记语法重写 build_user,使其行为完全相同但没有重复的email和username。
顺序可以打乱。在这里,我们正在创建`User`结构的一个新实例,它有一个名为`email`. 我们想将`email`字段的值 设置为函数`email`参数中的`build_user`值。因为`email`字段和`email`参数同名,所以我们只需要写`email`而不是`email: email`。
![image-20230105142354224](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230105142354224.png)
## 使用其他实例创建新的值
![image-20230105151237732](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230105151237732.png)
更简单的写法如下。`..`指定未显式设置的其余字段应与给定实例中的字段具有相同的值。
![image-20230105151914177](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230105151914177.png)
## tuple structs
元组结构没有字段关联的名称,它们只有**字段的类型**。而相较于元组,在管理相同类型的数据时,有更加直观。当你想要给整个元组一个名字并将元组与其他元组区分开来时可以使用此结构。
例如,你可能会使用元组结构来表示颜色,并希望为每种颜色分配一个名字。你可以定义一个元组结构 Color,其中存储了三个 i32 类型的值,分别表示红,绿和蓝色分量。可以此来定义其他颜色。
![image-20230105154919875](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230105154919875.png)
注意 black 和 origin 值是不同类型,因为它们是不同元组结构体的实例。每个您定义的结构体都是自己的类型,即使结构体内的字段可能具有相同的类型。例如,接受类型 Color 的参数的函数不能作为 Point 类型的参数
## 没有任何字段的类单元(*unit-like* )结构
没有任何字段的结构被称为类单元结构,因为它们的行为类似于我们在“元组类型”部分()中提到的单元类型。**当需要在某种类型上实现特征但没有要存储在类型本身中的任何数据时**,类似单元的结构会很有用。
要定义`AlwaysEqual`,我们使用`struct`关键字、我们想要的名称,然后是分号。不需要大括号或圆括号!`AlwaysEqual`然后我们可以用类似的方式在变量中获取一个实例`subject`:使用我们定义的名称,不带任何大括号或圆括号。
![image-20230105161458114](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230105161458114.png)
## 结构体的所有权
![image-20230105162555140](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230105162555140.png)

@ -0,0 +1,63 @@
## match 控制流
match,它允许你将值与一系列模式进行比较,然后根据匹配的模式执行代码。模式可以由**文字值**、**变量名**、**通配符**和许多**其他内容**组成。
![image-20230109092754370](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230109092754370.png)
对于 if,表达式需要返回一个布尔值,但是在这里,它可以返回任何类型。
一个臂有两部分:一个模式和一些代码。这里的第一个臂有一个模式,即值 Coin::Penny,然后是 => 运算符,它将模式和要运行的代码分隔开来。在这种情况下,代码只是值 1。每个臂与下一个臂用**逗号隔开**。
如果匹配臂的代码很短,我们通常不使用花括号。如果要在匹配臂中运行多行代码,则必须使用花括号,并且臂之后的**逗号就可选了**
![image-20230109095933017](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230109095933017.png)
## 绑定到值的模式
匹配臂的另一个有用特性是它们可以绑定到与模式匹配的值的部分。
![image-20230109104321768](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230109104321768.png)
## Matching with Option<T>
对枚举进行匹配,将变量绑定到内部数据,然后根据它执行代码。
![image-20230109110432810](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230109110432810.png)
## Matches Are Exhaustive
使用 match 语句时,必须考虑到**所有可能的**情况(包括None),否则编译器会报错。这与其他语言中的 switch 语句不同,在其他语言中,switch 语句通常可以有一个 default 分支,如果没有匹配到任何一个条件,则执行 default 分支。
错误写法如下:
![image-20230109112300779](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230109112300779.png)
## Catch-all Patterns and the _ Placeholder
在前两个臂中,模式是 3 和 7 的字面值。对于覆盖所有其他可能值的最后一个臂,模式是我们选择的命名为 other 的变量。其他臂运行的代码使用该变量,通过将其传递给 move_player 函数。
即使我们没有列出 u8 可以具有的所有可能值,该代码也可以编译,因为最后一个模式将匹配未列出的所有值。这个通用模式满足 match 必须是完全的要求。请注意,我们**必须将通用臂放在最后**,因为模式按顺序计算。如果我们将通用臂放在前面,其他臂将永远不会运行,所以如果在通用臂之后添加臂,Rust 会给出警告!
Rust 还有一个模式,我们可以在想要通用臂但不想在通用臂模式中使用值时使用:**_ 是一个特殊模式,它匹配任何值,并不绑定到该值**。这告诉 Rust 我们不会使用该值,因此 **Rust 不会给出未使用变量的警告**
![image-20230109140725594](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230109140725594.png)
通用臂之后添加臂:
![image-20230109141155765](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230109141155765.png)
可以在想要通用臂但不想在通用臂模式中使用值时使用_: _是一个特殊模式,它匹配任何值,并不绑定到该值。这告诉 Rust 我们不会使用该值,因此 Rust 不会给出未使用变量的警告。
![image-20230109142652724](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230109142652724.png)
除了3或7以外的任何东西,不会发生任何其他事情
![image-20230109143000827](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230109143000827.png)

@ -0,0 +1,64 @@
## if-let
if let 语法允许你将 if 和 let 组合成一种简洁的方式,用于处理与一个模式匹配的值,同时忽略其余的值。
```rust
if let pattern = value {
// code block
} else {
// code block
}
```
![image-20230109150811762](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230109150811762.png)
### 使用match
![image-20230109150359124](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230109150359124.png)
### 使用if-let
if let 语法使用模式和表达式,用**等号**隔开。它的工作方式与 **match** 相同,其中表达式给定给 match,模式是它的第一个臂。在这种情况下,模式是 Some(max),max 绑定到 Some 内部的值。然后我们可以在 if let 块的主体中像在相应的 match 臂中一样使用 max。如果值不匹配模式,则不会运行 if let 块中的代码。
使用 if let 意味着打的字比较少,缩进次数比较少,所需的代码也比较少。但是,你会失去 match 强制执行的**详尽检查**。选择 match 和 if let 取决于你在特定情况下正在进行的操作,以及是否放弃详尽检查以获得简洁性是否合适。
换句话说,你可以把 if let 看作是 match 的**语法糖**,当值与一个模式匹配时运行代码忽略所有其他值。if let 语法也可以有一个 else 分句。
![image-20230109150636791](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230109150636791.png)
### if let - else
match 写法
![image-20230109153859735](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230109153859735.png)
if let -else写法
![image-20230109154333707](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230109154333707.png)
### 通用臂
1. match 语句中,_ 总可以替代 None
2. 使用变量名作为通用臂的好处是,你可以在代码块中使用这个变量,比使用 _ 更加灵活。但是,使用变量名作为通用臂时,要注意,通用臂必须放在所有其他臂的最后,因为它是最后一个匹配的,如果放在前面,则永远不会执行其他臂。
### if let … else if let … else
```rust
if let pattern_1 = value_1 {
// code block 1
} else if let pattern_2 = value_2 {
// code block 2
} else {
// code block 3
}
```
如果 pattern_1 与 value_1 匹配成功,则执行代码块 1;如果 pattern_1 匹配失败,但 pattern_2 与 value_2 匹配成功,则执行代码块 2;如果所有条件都不成立,则执行代码块 3。
![image-20230109155041676](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230109155041676.png)

@ -0,0 +1,31 @@
# let 和 match
match 语法和 if let 语法都是用来匹配模式的。但是,它们有一些重要的区别:
1. 语法不同:match 语法的语法结构比 if let 语法的语法结构复杂。 match 语法的语法结构如下:
```rust
match EXPRESSION {
PATTERN => CODE,
PATTERN => CODE,
...
}
```
而 if let 语法的语法结构如下:
```rust
if let PATTERN = EXPRESSION {
CODE
}
```
2. 匹配的模式不同:match 语法可以匹配任何模式,而 if let 语法只能匹配单个模式。例如,可以使用 match 语法来匹配一个带有枚举值的元组,但是不能使用 if let 语法来匹配。
3. 可读性不同:由于 if let 语法的语法结构更简单,因此它可能更易于阅读和理解。但是,由于 match 语法在模式匹配时有更多的功能,因此在进行复杂的模式匹配时,match 语法可能更易于阅读和理解。
4. 可移植性不同:match 语法是 Rust 中的语言特性,而 if let 语法是 Rust 的语法糖(syntax sugar)。这意味着,if let 语法的实现是基于 match 语法的。因此,如果要在其他语言中使用相似的功能,可能需要自己实现 if let 语法。
5. 其他功能不同:match 语法具有更多的功能,例如可以使用守卫(guards)和多个分支(arms)来实现更复杂的逻辑。而 if let 语法仅能进行简单的单分支匹配。
总的来说,如果你的代码需要进行复杂的模式匹配,或者需要使用守卫、多个分支或其他功能,则应使用 match 语法。如果你的代码只需要进行简单的单分支匹配,则可以使用 if let 语法,以获得更简洁的代码。

@ -0,0 +1,135 @@
# Enums and Pattern Matching
枚举是 Rust 中的一种类型,也称为 enums。枚举允许你通过枚举变量的可能变体来定义一个类型。首先,我们将定义并使用枚举来展示枚举如何在数据中编码意义。
Option 是 Rust 中内置的**枚举类型**。Option<T> 表示一个值可以是 T 类型的值,也可以是什么都没有。它有两个可能的变体:Some(T) 和 None
## 定义枚举
枚举使你能够表示一个值是一组可能的值之一。关键字为enum。
![image-20230106142149652](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230106142149652.png)
### 直接使用枚举
直接将数据附加到枚举的变体,故不再需要结构体。
我们定义的每个枚举变体的名称也成为一个构造枚举实例的函数。 也就是说,IpAddr::V4()是一个带有 String 参数并返回 IpAddr 类型实例的函数调用。我们在定义枚举时自动获得了这个构造函数。
![image-20230106145517117](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230106145517117.png)
使用枚举而不是结构体还有另一个优点:每个变体都可以具有不同类型和数量的关联数据。 版本四型 IP 地址将始终具有四个**数字**组件,其值将在 0 到 255 之间。 如果我们想将 V4 地址存储为四个 u8 值,但仍想使用一个 **String** 值表示 V6 地址,则无法使用**结构体**(硬用会很麻烦)。 枚举很容易处理这种情况:
![image-20230106153120695](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230106153120695.png)
### 标准库提供的IpAddr([地址](https://doc.rust-lang.org/std/net/enum.IpAddr.html))
```rust
#![allow(unused)]
fn main() {
struct Ipv4Addr {
// --snip--
}
struct Ipv6Addr {
// --snip--
}
enum IpAddr {
V4(Ipv4Addr),
V6(Ipv6Addr),
}
}
```
1. \#![allow(unused)] 是 Rust 的一个编译器属性。这个属性告诉编译器在编译时**忽略未使用的变量、函数**等的警告。
2. 此代码说明您可以将任何类型的数据放入枚举变体中:例如,**字符串、数字类型或结构**。你甚至可以包括另一个**枚举**!
3. 即使标准库包含的`IpAddr`,我们仍然可以创建和使用我们自己的定义而不会发生冲突,因为我们没有将标准库的定义纳入我们的**范围**。
### Message枚举类型
这个枚举有四种不同类型的变体:
- `Quit`根本没有与之关联的数据。
- `Move`像结构一样命名字段。
- `Write`包括一个`String`.
- `ChangeColor`包括三个`i32`值。
![image-20230106161000110](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230106161000110.png)
只是枚举不使用 `struct`关键字并且所有变体都在类型下组合在一起`Message` 。以下结构可以包含与前面的枚举变体相同的数据:
```rust
struct QuitMessage; // unit struct
struct MoveMessage {
x: i32,
y: i32,
}
struct WriteMessage(String); // tuple struct
struct ChangeColorMessage(i32, i32, i32); // tuple struct
fn main() {}
```
枚举和结构之间还有一个相似之处:正如我们能够使用 定义结构上的方法一样`impl`(有点像多态了)。枚举在某些情况下可能是结构体的有用替代品,特别是当你想定义一个只能有一个固定集合值的类型时。 但是,枚举并不总是最佳选择。 在表示更复杂的数据结构时,结构体通常更灵活,并且更为适当。 使用最适合解决的问题的数据结构通常是一个好主意。
![image-20230106165212175](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230106165212175.png)
### Option枚举
Option 标准库中的枚举的。Option 类型编码了一种非常常见的场景,即值可能是某些东西,也可能是没有任何东西。
Rust 没有许多其他语言都有的 null 功能。rust没有空值,但是它有一个枚举,可以编码值存在或缺失的概念。 这个枚举是Option<T>
好处是:如果请求包含项目的列表中的第一个项目,则将获得一个值。如果请求空列表的第一个项目,则将获得 nothing。在类型系统中表达这个概念意味着编译器可以检查您是否处理了所有应该处理的情况;这个功能可以防止其他编程语言中非常常见的错误。
Option<T>标准库定义。<T>是一个泛型类型参数,可以保存任何类型的一个数据。
```rust
pub enum Option<T> {
None,
Some(T),
}
```
![image-20230108210445242](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230108210445242.png)
此例子中Option<T>具体类型。
![image-20230108213055337](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230108213055337.png)
Option<T> 和 null 的一个很重要的区别在于,Option<T> 是在**编译时**进行类型检查的。这意味着如果你尝试将一个 Option<T> 当做 T 来使用,你会在编译时得到一个错误。这是因为 **Option<T> 和 T 是不同的类型**,所以编译器会提醒你处理这种情况。但是在其他语言中,null 可以随意转换成其他类型,如果你尝试将 null 当做某个类型使用,那么你可能会在**运行时**得到一个错误,这就是 Tony Hoare 所说的“十亿美元的错误”。Option<T> 可以在编译时帮助你避免这种错误。
![image-20230108213602623](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230108213602623.png)
Rust 中无法将 i8 和 Option<i8> 相加,因为它们是不同的类型。当我们在 Rust 中有一个 i8 类型的值时,编译器会确保我们始终拥有一个有效值。我们可以放心地使用该值,而无需在使用之前检查 null。只有当我们有一个 Option<i8>(或我们正在处理的任何类型的值)时,我们才需要担心可能没有值,编译器会确保我们在使用该值之前处理该情况
**执行 T 操作之前,你必须将 Option<T> 转换为 T**。通常,这可以帮助捕获 null 最常见的问题之一:假设某物不是 null,但实际上是 null。
消除假设非 null 值的风险有助于让你对代码更有信心。为了有一个可能为 null 的值,你必须通过将该值的类型设置为 Option<T> 显式选择。然后,当你使用该值时,**你必须显式处理值为 null 的情况**。所有类型不是 Option<T> 的值都可以安全地假设该值不为 null。这是 Rust 为了限制 null 的普遍性并提高 Rust 代码安全性而做出的一个有意的设计决策
1. 可以使用 match 表达式来处理 Option<T> 的每个变体。
2. unwrap来尝试获取 Option<T> 的内部值,如果是 None 变体,unwrap 会 panic
3. expect 与 `unwrap` 类似,但允许你指定当 Option<T> 为 None 变体时 panic 时显示的错误信息。
![image-20230108214914594](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230108214914594.png)

@ -0,0 +1,58 @@
## 引用模块树中项目的路径
路径的两种形式:
1. 绝对路径是从 crate 根开始的完整路径;对于来自外部 crate 的代码,绝对路径以 crate 名称开头,对于来自当前 crate 的代码,它从字面量 crate 开始。
2. 相对路径从当前模块开始,并使用 self,super 或当前模块中的标识符。
无论是绝对路径还是相对路径,都要由一个或多个由双冒号(::)分隔的标识符来提示。
![image-20230111101105942](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230111101105942.png)
选择是使用相对路径还是绝对路径是您将根据您的项目做出的决定,并且取决于您是否更有可能将项目定义代码与使用该项目的代码分开移动或一起移动。例如,如果我们将`front_of_house`模块和`eat_at_restaurant` 函数移动到名为 的模块`customer_experience`中,我们需要将绝对路径更新为`add_to_waitlist`,但相对路径仍然有效。但是,如果我们将`eat_at_restaurant`函数单独移动到名为 的模块`dining`中,调用的绝对路径`add_to_waitlist`将保持不变,但相对路径需要更新。
**我们通常倾向于指定绝对路径,因为我们更有可能希望彼此独立地移动代码定义和项目调用。**
**在 Rust 中,所有项目(函数,方法,结构体,枚举,模块和常量)默认对父模块私有。如果你想让一个像函数或结构体的项目私有,可以把它放在一个模块中。**
### pub关键字
![image-20230111104040810](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230111104040810.png)
`pub`在前面添加关键字`mod hosting`使模块公开。通过此更改,如果我们可以访问`front_of_house`,我们就可以访问`hosting`。但*内容*`hosting`仍然是私有的;公开模块并不会公开其内容。模块上的`pub`关键字只允许其祖先模块中的代码**引用它**,而不能访问其**内部代码**。因为模块是容器,所以我们只能通过公开模块来做很多事情;我们需要更进一步,选择公开模块中的一项或多项。
### super实现相对路径
我们可以使用 super 在路径开头构建**相对路径**,而不是在当前模块或 crate 根中构建。这类似于使用 **..** 语法开始文件系统路径。使用 super 允许我们引用一个我们知道在父模块中的项目,这可以使重新排列模块树变得更容易,因为当模块与父模块密切相关,但父模块可能会在未来的模块树中移动。
![image-20230111142630499](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230111142630499.png)
fix_incorrect_order 函数在 back_of_house 模块中,所以我们可以使用 super 去到 back_of_house 的父模块,在这种情况下是根模块 **crate**。然后,我们查找 deliver_order 并找到它。
### 公开结构和枚举
我们还可以使用 pub 来指定结构体和枚举为公共的,但是使用 pub 与结构体和枚举有一些额外的细节。如果在结构体定义前使用 pub,则使结构体公共,但**结构体的字段**仍将是**私有的**。我们可以根据情况分别对每个字段进行公开或不公开。
![image-20230111153714282](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230111153714282.png)
需要注意的是,由于 back_of_house::Breakfast 具有私有字段,因此该结构体需要提供一个公共关联函数来构造 Breakfast 的实例(我们在这里将其命名为 summer)。如果 Breakfast 没有这样的函数,我们就不能在 eat_at_restaurant 中创建 Breakfast 的实例,因为我们无法在 eat_at_restaurant 中设置私有 seasonal_fruit 字段的值。
![image-20230111163833879](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230111163833879.png)
#### 公开枚举
如果我们公开一个枚举,那么它的所有变体都是公开的(字段都是公开的)。我们只需要`pub`在`enum`关键字之前。
![image-20230111170619073](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230111170619073.png)
如果枚举的变量不是公共的,那么它们将没有用处;在每种情况下都要用 pub 注释所有枚举变量将是麻烦的,因此枚举变量的默认值是**公共的**。结构体在没有公共字段的情况下往往很有用,因此结构体字段遵循一般规则,即除非使用 pub 注释,否则一切都是私有的。
在Rust中,结构体或枚举的方法默认都是私有的,不论结构体或枚举是公共的还是私有的。如果你希望让结构体或枚举的方法能被其他代码访问,那么需要在方法前使用 pub 修饰符。

@ -0,0 +1,187 @@
## 使用use将路径纳入范围
使用use关键字创建路径,使**范围内**其他地方可以使用更短的名称。
![image-20230112091925053](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230112091925053.png)
使用 use 和路径在作用域中类似于文件系统中创建一个符号链接。 通过在 crate 根中添加 use crate::front_of_house::hosting,hosting 现在是该作用域中有效的名称,就像 hosting 模块已在 crate 根中定义一样。 使用的路径也会检查隐私,就像其他路径一样。
**use 只在特定作用域中创建了快捷方式。**将 eat_at_restaurant 函数移动到新的子模块 customer 中,然后它是不同的作用域,因此函数体将不能编译。
![image-20230112093658359](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230112093658359.png)
该警告表示该 use 在其作用域中不再使用! 要解决此问题,请将 use 移动到 customer 模块中,或在 child customer 模块中使用 super::hosting 引用父模块中的快捷方式。
![image-20230112093820390](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230112093820390.png)
在使用引入结构、枚举和其他项时`use`,惯用的做法是指定完整路径。
![image-20230112100654208](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230112100654208.png)
### 处理相同名称问题
不用as:
```rust
use std::fmt;
use std::io;
fn function1() -> fmt::Result {
// --snip--
Ok(())
}
fn function2() -> io::Result<()> {
// --snip--
Ok(())
}
```
使用as处理相同名称
![image-20230112103459332](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230112103459332.png)
### pub use (未实践,有点迷茫)
当我们使用 use 关键字将名称带入作用域时,新作用域中可用的名称是**私有**的。 为了使调用我们代码的代码能够像在其作用域中定义了该名称一样引用该名称,我们可以结合 pub 和 use。 此技术称为**重新导出**,因为我们正在将项目带入作用域,并使该项目可供其他人带入其作用域。
`pub use`语句主要用于控制您的crate的公共接口,通过重新导出某些项目并使它们更易于访问crate的用户。这可以使API更方便和用户友好,因为**它减少了访问某些项目所需的嵌套层数**,并允许用户以与crate的内部结构不同的方式思考领域。
- `use` 关键字是将某个路径中的名称引入当前作用域,使之在当前作用域内直接可用。
- `pub use` 关键字是将某个路径中的名称引入当前作用域并且标记为公共,使之在当前作用域外也可用。
![image-20230112145135795](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230112145135795.png)
在此更改之前,外部代码调用该函数 `restaurant::front_of_house::hosting::add_to_waitlist()`。现在`pub use`已经从根模块中重新导出了`hosting`模块,外部代码现在可以改用该路径`restaurant::hosting::add_to_waitlist()`。
### 使用外部包
同第二章猜谜游戏。
1. 在Cargo.toml 文件内添加 dependencies、
![image-20230112150952802](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230112150952802.png)
2. 将`Rng`特征带入作用域并调用`rand::thread_rng`函数:
![image-20230112151307961](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230112151307961.png)
[cartes.io](https://crates.io/)
请注意,标准`std`库也是我们包外部的一个板条箱。因为标准库是随 Rust 语言一起提供的,所以我们不需要更改*Cargo.toml*来包含`std`。但是我们确实需要引用它`use`以将项目从那里带入我们的包范围。例如,`HashMap`我们将使用这一行:
```rust
#![allow(unused)]
fn main() {
use std::collections::HashMap;
}
```
### 解决相同crate重复use问题
```rust
// --snip--
use std::cmp::Ordering;
use std::io;
// --snip--
```
修改为:
```rust
// --snip--
use std::{cmp::Ordering, io};
// --snip--
```
当两个use一个为另一个子路径时
```rust
use std::io;
use std::io::Write;
```
可写成:
```rust
use std::io::{self, Write};
```
### *操作符
如果我们想要将路径中定义的所有公共项目都带入作用域中,我们可以指定该路径,然后跟着 * 通配符:
```rust
#![allow(unused)]
fn main() {
use std::collections::*;
}
```
该`use`语句将 中定义的所有公共项目`std::collections`带入当前范围。使用 glob 运算符时要小心!Glob 可以让你更难分辨哪些名字在范围内,以及在你的程序中使用的名字是在哪里定义的。
测试时经常使用 glob 运算符,将所有被测试的东西都带入`tests`模块;
## 拆分mod为不同文件
注意,**您只需要在模块树中使用 mod 声明加载一次文件**。一旦编译器知道文件是项目的一部分(并且因为您放置 mod 语句的位置而知道代码在模块树中的位置),项目中的其他文件应该使用声明中的路径来引用**已加载文件的代码**,如“在模块树中引用项目的路径”部分所述。换句话说,mod 不是其他编程语言中可能看到的“include”操作(在每个文件中都需要使用 include 来引入头文件。)。
接下来,我们将提取托管模块到其自己的文件中。这个过程有点不同,因为托管是 front_of_house 的子模块,而不是根模块。我们将托管文件放在一个新目录中,该目录将命名为其模块树中祖先的名称,在这种情况下为 src/front_of_house/。
![image-20230112162129076](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230112162129076.png)
接着拆分hosting
![image-20230112170412078](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230112170412078.png)
### 备用文件路径
到目前为止,我们已经介绍了 Rust 编译器使用的最标准文件路径,但 Rust 还支持一种旧式文件路径。对于在 crate 根目录中声明的名为 front_of_house 的模块,编译器将在以下位置查找模块的代码:
- src/front_of_house.rs(我们讨论过的)
- src/front_of_house/mod.rs(旧式,仍然支持的路径)
对于名为 hosting 的 front_of_house 的子模块,编译器将在以下位置查找模块的代码:
- src/front_of_house/hosting.rs(我们讨论过的)
- src/front_of_house/hosting/mod.rs(旧式,仍然支持的路径)
**如果您对同一模块使用了两种样式,则会得到编译错误**。在同一项目中为不同模块使用混合样式是允许的,但可能会使人们在您的项目中导航时感到困惑。
使用名为 mod.rs 的文件的样式的主要缺点是,您的项目可能会有许多名为 mod.rs 的文件,在您的编辑器中同时打开它们时可能会感到困惑。
将模块代码移动到单独的文件中, 并保持模块树不变. 这样做可以让代码更容易维护和扩展, 当模块变得越来越大时可以将它们移动到新的文件中。
pub use crate::front_of_house::hosting 语句在src/lib.rs中, 表示将 crate::front_of_house::hosting 模块暴露在crate根作用域下, 其他模块可以直接使用hosting模块中的函数.
use 关键字并不影响编译器编译文件, 它只是让编译器知道这个模块在哪里, 以便在编译时能够找到这个模块. mod关键字声明了模块,并且Rust会在与模块同名的文件中查找该模块的代码.
## 总结
Rust 允许您将一个包拆分成多个 crates 和一个 crate 拆分成模块,以便您可以从一个模块引用另一个模块中定义的项目。您可以通过指定绝对或相对路径来实现这一点。可以使用 use 语句将这些路径带入作用域,以便在该作用域中多次使用项目时使用较短的路径。默认情况下,模块代码是私有的,但您可以通过添加 pub 关键字来使定义公共。

@ -0,0 +1,184 @@
# 使用package,crates和modules管理项目
Rust 具有许多功能,可让您管理代码的组织,包括公开哪些细节,哪些细节是私有的,以及程序中每个范围内的名称。这些功能有时统称为*模块系统*,包括:
- **Packages:** Cargo 的一项功能,可让您构建、测试和共享 crate
- **Crates:**生成库或可执行文件的模块树
- **Modules** and **use:**让您控制路径的组织、范围和隐私
- **Paths:**一种命名项目的方式,例如结构、函数或模块
## packages and crates
模块是 Rust 编译器在同一时间考虑的最小代码单元。即使你使用 rustc 而不是 cargo,并且仅传递一个源代码文件,编译器也会将该文件视为模块。模块可以包含其他模块,并且这些模块可以在随同模块一起编译的其他文件中定义。
模块可以以两种形式之一的形式出现:**二进制模块**或库模块。二进制模块是你可以编译成**可执行文件的**程序,例如命令行程序或服务器。每个二进制模块必须有一个名为 main 的函数,它定义了可执行文件运行时发生的事情。我们迄今为止创建的所有模块都是二进制模块。
库模块没有 **main** 函数,也**不能编译成可执行文件**。相反,它们定义的是旨在与多个项目**共享**的功能。例如,我们在第 2 章中使用的 rand 模块提供了生成随机数的功能。大多数时候,当 Rust 程序员说“模块”时,他们指的是**库模块**,并且将“模块”和一般的编程概念“库”交替使用。
包则是一组**相关的模块**的集合。包中的所有模块都共享同一个名称空间。包通常由多个文件组成,这些文件可能位于多个目录中。包还可以**依赖于其他包**,这意味着它们可以使用其他包中定义的功能。
在 Rust 中,每个文件都属于一个模块,而每个目录都属于一个包。在同一包中的所有文件都共享同一个**名称空间**。这意味着如果一个文件定义了一个函数或变量,那么这个函数或变量就可以在同一包中的**其他文件**中使用。
模块的根目录是 Rust 编译器从中启动的源文件,并构成了你的模块的根模块(我们将在“定义模块以控制作用域和隐私”一节中详细解释模块)。
包是**一个或多个模块**的集合,它提供了一组功能。包包含一个 Cargo.toml 文件,该文件描述了如何构建这些模块。Cargo 实际上是一个包,其中包含了你一直在使用的命令行工具的**二进制模块**。Cargo 包还包含了一个库模块,二进制模块依赖于此库模块。其他项目也可以依赖于 Cargo 库模块,以使用与 Cargo 命令行工具使用的相同逻辑。
包可以包含任意数量的二进制模块,但至多只能包含**一个库模块**(因为包的目的是提供一组功能,而不是多个不同的功能集。)。包必须至少包含一个模块,无论是库模块还是二进制模块。
![image-20230110095230620](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230110095230620.png)
使用 ls 查看 Cargo 创建了什么。在项目目录中,有一个 Cargo.toml 文件,提供了一个包。还有一个 src 目录,其中包含 main.rs。如果一个包只包含 **src/main.rs**,则意味着它只包含一个名为 my-project 的二进制模块。这意味着这个包只包含一个可执行文件,该文件的名称与包的名称相同(在这种情况下为 my-project)。
如果一个包同时包含 **src/main.rs****src/lib.rs**,则它有两个模块:一个名为 my-project 的**二进制模块**和一个同名的**库模块**。这意味着这个包包含一个**可执行文件**和一个**库**。
包可以通过将文件放在 **src/bin** 目录中拥有多个二进制模块:每个文件将是一个单独的二进制模块。这意味着你可以在同一个包中提供多个可执行文件,每个文件都有自己的名称。在这种情况下,你可以使用 Cargo 进行构建,并指定要构建哪个二进制模块。
一个包下多个二进制模块,一个库模块:
![image-20230110101632310](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230110101632310.png)
运行方式:
![image-20230110101543444](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230110101543444.png)
多个二进制模块时运行main
如果你的包中包含多个二进制模块,或者你希望运行一个名称不为 main.rs 的二进制模块,那么你需要指定二进制模块的名称。你可以使用 cargo build --bin 和 cargo run --bin 命令,其中 bin 后面跟着二进制模块的名称。
![image-20230110102314287](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230110102314287.png)
## 定义模块控制范围和隐私
模块系统是 Rust 中用来组织代码的一种机制。在本节中,我们将讨论模块以及模块系统的其他部分,包括允许你命名项目的路径;将路径引入作用域的 use 关键字;以及使项目变为公有的 pub 关键字。我们还将讨论 as 关键字、外部包和 glob 操作符。
crate root 通常是 src/lib.rs;对于二进制 crate,crate root 通常是 src/main.rs。如果你的包同时包含了 src/lib.rs 和 src/main.rs 两个文件,那么 src/lib.rs 是库模块的 crate root,src/main.rs 是二进制模块的 crate root。当你使用 cargo build 或者 cargo run 编译和运行这个包时,cargo 会根据你的命令来选择编译哪个 crate root。如果你使用 cargo build,cargo 会编译 src/lib.rs;如果你使用 cargo run,cargo 会编译 src/main.rs。
### 模块备忘录
以下提供了关于模块、路径、use 关键字和 pub 关键字在**编译器**中的工作方式,以及大多数开发人员如何组织代码的快速参考。
1. **Start from the crate root:** 编译crate时,编译器首先在crate根文件中查找要编译的代码(库crate一般是src/lib.rs, 对于二进制crate通常是src/main.rs)。
2. **Declaring modules:** 在crate根文件中,你可以声明新的模块。比如可以用 **mod garden;**来声明一个“garden”模块。编译器会在以下位置选择模块的代码:
1. 直接在mod garden后面定义,使用花括号替代分号。
1. 在文件src/garden.rs中
1. 在文件src/garden/mod.rs中。mod.rs 文件是 Rust 中的一种特殊文件。它允许你在一个文件夹中组织你的模块定义,并且在外部看起来就像一个普通的模块一样。使代码更容易阅读和维护。
3. **Declaring submodules:** 在crate root以外的任何文件中,都可以声明子模块。例如,你可以在src/garden.rs中声明 **mod vegetables; **编译器将在以父模块命名的目录中查找子模块代码:
1. 直接在mod vegetable后面,使用花括号替代分号。
2. 文件src/garden/vegetables.rs
3. 文件src/garden/vegetables/mod.rs
4. **Paths to code in modules:** 当一个模块成为你的 crate 的一部分时,你可以在 crate 中的任何地方引用该模块中的代码,只要隐私规则允许,使用该代码的路径。例如,在 garden vegetables 模块中的 Asparagus 类型将在 crate::garden::vegetables::Asparagus 处找到。
5. **Private vs public:** 默认情况下,模块内的代码是不能被它的父模块访问的。如果想让模块可以被父模块访问,就需要在模块定义时使用 pub mod 而不是 mod。如果想让公共模块内的项也可以被其他地方访问,需要在项的定义前加上 pub 关键字。
6. **The use keyword:** 在一个范围中,使用关键字可以为项目创建快捷方式,以减少长路径的重复。在任何可以引用crate::garden::vegetables::Asparagus的范围中,您可以使用 use crate::garden::vegetables::Asparagus; 创建一个快捷方式,从此以后,只需要编写 Asparagus 即可在该范围中使用该类型。
引用其他模块:
![image-20230110152746991](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230110152746991.png)
vegetables.rs代码:
```rust
#[derive(Debug)]
pub struct Asparagus {}
pub fn hello_world(){
println!("hello world vegetables!!");
}
```
garden.rs代码:
```rust
pub mod vegetables;
pub fn hello_world(){
println!("hello garden");
}
```
main.rs代码:
```rust
use crate::garden::vegetables::Asparagus;
pub mod garden;
fn main() {
let plant = Asparagus{};
garden::hello_world();
crate::garden::vegetables::hello_world();
garden::vegetables::hello_world();
println!("I'm growing {:?}!", plant);
}
```
![image-20230110152650259](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230110152650259.png)
**bin目录一般用于存放可执行文件,也就是项目中的主程序。这些文件都是独立的可执行文件,不需要其它文件的支持。**
如果你把garden.rs放在bin目录下,那么garden.rs就不是项目的crate root了,因此在编译时不会被编译,也就**不能在main中直接使用**了。你需要将garden.rs中的代码移动到src目录下,并修改mod garden引入路径。指定cargo run --bin backyard 是为了运行bin目录下的main.rs。
故而在garden.rs写main函数永远不会执行,src/main.rs是默认的crate root文件。
**使用mod.rs的好处。**
![image-20230110113332025](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230110113332025.png)
### 将相关代码分组到模块中
在Rust中,模块(module)提供了一种将代码在crate中进行组织和重复使用的方式。模块还允许我们控制项目的隐私,因为模块内的代码默认是私有的。私有项目是外部代码不能使用的实现细节。我们可以选择将模块和其中的项目公开,这样就可以将它们暴露给外部代码使用和依赖。
餐厅库的定义:
![image-20230110162401871](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230110162401871.png)
前台模块的实现:
![image-20230110164745830](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230110164745830.png)
我们使用关键字 mod 和模块的名称(在这种情况下是 front_of_house)来定义一个模块。然后将模块的正文放在大括号内。在模块中,我们可以放其他模块,如在这种情况下的 hosting 和 serving 模块。模块还可以包含其他项目的定义,如结构体、枚举、常量、特征,以及函数。
通过使用模块,我们可以将相关的定义分组并命名它们的关系。使用此代码的程序员可以根据组来导航代码,而不是必须阅读所有定义,这样可以更容易地找到对他们有关的定义。向此代码添加新功能的程序员将知道将代码放置在哪里以保持程序的组织性。
先前,我们提到 src/main.rs 和 src/lib.rs 称为 crate roots。它们名称的原因是这两个文件中的内容形成了一个名为 **crate** 的模块,该模块位于 crate 模块结构的根部,被称为**模块树**。请注意,整个模块树都以名为 crate 的隐式模块为根。
```rust
crate
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
```

@ -0,0 +1,176 @@
### 字符串拼接
#### +运算符和format!宏连接
```
+ 运算符使用以下add方法
fn add(self, s: &str) -> String {
```
在调用add时使用&s2是因为编译器可以将**&String参数强制转换为&str**。当我们调用add方法时,Rust使用deref强制转换。
![image-20230116161123068](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230116161123068.png)
使用+操作符会失去第一个参数的所有权,同时在处理多个字符串拼接时会特别笨拙。以下为使用format!宏进行拼接,不会失去所有权(使用引用)。
![image-20230116162822236](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230116162822236.png)
### 字符串索引
**Rust 字符串不支持索引、**
![image-20230116163655653](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230116163655653.png)
#### 内部表示
一个字符串是 Vec<u8>的封装。当以 UTF-8 编码时,这些字母中的每一个都占用 1 个字节。但当以 Unicode 编码时,每一项占用两个字节。**字符串字节的索引并不总是与有效的 Unicode 标量值相关联。** UTF-8 是一种将 Unicode 编码转换成字节序列的转换格式。
![image-20230116165523344](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230116165523344.png)
因为存储的单元字节大小不同,故而直接通过下标取值有时会取到错误值。为了避免返回意外值并导致可能无法立即发现的错误,Rust 根本不编译此代码,并在开发过程的早期防止误解。
#### 字节和标量值和字形群
关于 UTF-8 的另一点是,从 Rust 的角度来看,实际上有三种相关的方式来查看字符串:作为字节、标量值和字素簇(最接近我们所说的*字母*)。
印地语单词“नमस”,它存储为一个`u8`值向量,如下所示:
```
[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164,
224, 165, 135]
```
18个字节,这就是计算机最终存储此数据的方式。如果我们将它们视为 Unicode 标量值,这就是 Rust 的`char`类型,那么这些字节如下所示:
```
['न', 'म', 'स', '', 'त', '']
```
这里有六个`char`值,但第四个和第六个不是字母:它们是本身没有意义的变音符号。最后,如果我们将它们视为字素簇,我们会得到人们所说的构成印地语单词的四个字母:
```
["न", "म", "स", "त"]
```
Rust 提供了不同的方式来解释计算机存储的原始字符串数据,这样每个程序都可以选择它需要的解释,而不管数据使用的是哪种人类语言。
Rust 不允许我们通过索引 a`String`来获取字符的最后一个原因是索引操作预计总是需要常数时间 (O(1))。但是不能保证 a 的性能`String`,因为 Rust 必须遍历从头到索引的内容以确定有多少个有效字符。
### 字符串切片
可以使用索引创建字符串切片,但需谨慎,会产生意外panic。
![image-20230129100107046](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129100107046.png)
当对二个字节字符取一个字节时,会产生如下错误:
![image-20230129100347688](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129100347688.png)
### 迭代字符串
`chars`方法返回每个字符,`bytes`方法返回每个原始字节。
**有效的 Unicode 标量值可能由超过 1 个字节组成。**
![image-20230129103709654](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129103709654.png)
## HashMap<K,V>
### 创建HashMap
首先从标准库引用HashMap。哈希映射将其数据存储在堆上。哈希映射是同类型的: 所有键必须具有相同的类型,所有值也必须具有相同的类型。
![image-20230129105734858](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129105734858.png)
### 访问HashMap
get方法返回一个Option<&V>。如果哈希映射中该键没有值,`get`将返回`None`。
`unwrap_or()` 函数是 Rust 中 `Option` 类型的一个常用函数,它用于在值为 `None` 时返回一个默认值。它接受一个参数,在 `Option` 类型为 `None` 时返回该参数。
![image-20230129111116825](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129111116825.png)
#### 遍历HashMap
以任意顺序打印每一对。
![image-20230129112257955](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129112257955.png)
### HashMap所有权
对于实现`Copy`特征的类型,如`i32`,值被复制到哈希映射中。对于拥有的值,如`String`,这些值将被移动,哈希映射将成为这些值的所有者。
![image-20230129113639017](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129113639017.png)
#### 存储字符串引用(保留所有权)
![image-20230129114217635](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129114217635.png)
但是可能会出现这种情况:
![image-20230129115000375](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129115000375.png)
推荐使用clone:
![image-20230129115130528](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129115130528.png)
### 更新HashMap的值
1. 覆盖,插入了两个相同key
![image-20230129132045261](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129132045261.png)
2. 仅在键不存在时添加**键和值**。
如果该键确实存在于哈希映射中,则现有值应保持原样。如果键不存在,则插入它并为其赋值。`entry`将您要检查的密钥作为参数。该`entry`方法的返回值是一个枚举`Entry`,代表一个可能存在也可能不存在的值。
`or_insert` 方法被定义为如果该键存在,则`Entry`返回对相应`Entry`键值的可变引用,如果不存在,则将参数作为该键的新值插入,并返回对新值的可变引用。
![image-20230129132647150](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129132647150.png)
3. 基于旧值更新值
split_whitespace方法返回 中值的子片的迭代器,由空格分隔text。该`or_insert`方法返回一个可变引用 ( `&mut V`) 到指定键的值。这里我们将可变引用存储在`count`变量中,因此为了分配给该值,我们必须首先`count`使用星号 (`*`) 取消引用。
![image-20230129134200025](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129134200025.png)
### 哈希函数
默认情况下,HashMap使用一种名为SipHash的哈希函数,可以提供对哈希表的拒绝服务(DoS)攻击的抵抗力。这不是最快的哈希算法,但是为了更好的安全性而降低性能的权衡是值得的。如果您对代码进行了分析并发现默认哈希函数对您的目的太慢,您可以通过指定其他函数来切换。哈希器是一种实现BuildHasher特征的类型。

@ -0,0 +1,378 @@
# Common Collections
Rust 的标准库包含了许多有用的数据结构,称为**集合**。大多数其他数据类型表示一个特定的值,但集合可以包含多个值。与内置数组和元组类型不同,这些集合指向的数据存储在**堆**上,这意味着数据量不需要在编译时知道,可以随着程序运行而增长或缩小。
Rust 程序中经常使用的三种集合:
- 向量允许存储可变数量的值。
- 字符串是字符的集合。我们之前提到过 String 类型,但在本章中我们将深入讨论。
- 哈希映射允许将值与特定的键相关联。它是更一般的数据结构映射的特定实现。
[文档](https://doc.rust-lang.org/std/collections/index.html)
## Vectors
Vec<T> 向量。向量允许您在单个数据结构中存储多个值,将所有值放在**内存中相邻的位置**。向量只能存储相同类型的值。
### 创建新向量
1, 创建空向量。需指定类型,因为rust不知道我们打算存储什么样的元素。**向量是使用泛型实现的。**
```rust
fn main() {
let v: Vec<i32> = Vec::new();
}
```
2. 创建带有初始值的向量。Rust 会推断出你想要存储的值的类型,所以你很少需要做这种类型注释。
```rust
fn main() {
let v = vec![1, 2, 3];
}
// v 的类型是Vec<i32>。整数类型是i32 因为它是默认的整数类型
```
![image-20230113095210803](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230113095210803.png)
### 读取向量元素
访问向量有两种方法,索引或使用get方法。
![image-20230113102817517](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230113102817517.png)
使用get方法时。将得到一个Option<&T>。
当取值越界时: 1. 使用索引或直接抛出Error;
![image-20230113105927546](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230113105927546.png)
2. 使用get会返回None而不出现panic, 可通过match None处理。
![image-20230113110144920](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230113110144920.png)
#### 向量的工作方式与所有权
所有权规则:不能在同一范围内拥有可变和不可变引用的规则。对于vec有一个不可变引用时,在未归还所有权之前,无法对vec进行**新增**和**删除**和**修改**工作。
为什么对第一个元素的引用会关心向量末尾的变化呢?这个错误是由于向量工作的方式导致的:因为向量把值放在内存中相邻的位置,在向量末尾添加新元素可能需要分配新的内存并将旧元素复制到新空间,如果当前向量存储位置的相邻位置没有足够的空间。在这种情况下,第一个元素的引用将指向已经释放的内存。借用规则防止程序陷入这种情况。 [底层实现](https://doc.rust-lang.org/nomicon/vec/vec.html)
![image-20230113112124307](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230113112124307.png)
### 遍历向量
向量没有实现Copy trait 故遍历时需要提供引用,否则后续不能使用此向量。
![image-20230113140338487](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230113140338487.png)
使用引用
![image-20230113141725206](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230113141725206.png)
遍历并修改内容。使用可变引用,并使用`*`取消引用运算符获取值,
![image-20230113143017863](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230113143017863.png)
### 使用枚举存储多种类型
向量只能存储相同类型的值。但一个枚举的变体是在同一个枚举类型下定义的,故需要一个类型来表示不同类型的元素时,可以定义一个枚举。
![image-20230113153738574](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230113153738574.png)
Rust需要在编译时知道向量中将会有哪些类型,这样它就知道在堆上存储每个元素需要多少内存。我们还必须明确这个向量中允许哪些类型。如果Rust允许一个向量容纳任何类型,就有可能会有一些类型导致对向量元素执行操作时出现错误。
### 销毁向量时,元素一起被销毁
```rust
fn main() {
{
let v = vec![1, 2, 3, 4];
// do stuff with v
} // <- v goes out of scope and is freed here
}
```
当向量被销毁时,它的所有内容也会被销毁,这意味着它所持有的整数将被清理。借用检查器确保对向量内容的任何引用只在向量本身有效时使用。
## String
rust的三个难点:1. Rust喜欢暴露可能的错误,
2.字符串是一种比许多程序员认为的更复杂的数据结构,
3.UTF-8。
### 什么是字符串
Rust在**核心语言**中只有一种字符串类型,即通常以其借用形式&str出现的字符串片段**str**。它们是对存储在其他地方的UTF-8编码字符串数据的引用。
Rust **标准库**提供的 String 类型是一种可增长、可变、所有权、UTF-8 编码的字符串类型,而不是编入核心语言中的类型。当 Rustaceans 在 Rust 中提到“字符串”时,他们可能指的是 **String** 或字符串片段 &str 两种类型之一,而不仅仅是其中一种类型。尽管本节主要讨论 String,但两种类型在 Rust 标准库中都有广泛使用,且两种类型都是 **UTF-8** 编码的。
### 创建新字符串
字符串(String)也能使用与 Vec<T> 相同的许多操作,因为实际上字符串是在字节向量的基础上增加了一些额外的保证、限制和功能而实现的(底层是由一个 Vec<u8> 来实现的)。
to_string 方法,它可以在实现了 **Display** trait 的任何类型上使用,就像字符串字面量一样
```rust
fn main() {
let data = "initial contents";
let s = data.to_string();
// the method also works on a literal directly: 该方法也可以直接在文字上使用
let s = "initial contents".to_string();
}
```
使用String::from。代码等同于上面的。字符串使用许多不同的通用 API,为我们提供了很多选择。其中一些看似多余,但它们都有其用武之地!在这种情况下,`String::from`做 `to_string`同样的事情,所以你选择哪个是风格和可读性的问题。
```rust
fn main() {
let s = String::from("initial contents");
}
```
字符串是 UTF-8 编码的,因此我们可以在其中包含任何正确编码的数据。所有这些都是有效值`String`
```rust
fn main() {
let hello = String::from("السلام عليكم");
let hello = String::from("Dobrý den");
let hello = String::from("Hello");
let hello = String::from("שָׁלוֹם");
let hello = String::from("नमस");
let hello = String::from("こんにちは");
let hello = String::from("안녕하세요");
let hello = String::from("你好");
let hello = String::from("Olá");
let hello = String::from("Здравствуйте");
let hello = String::from("Hola");
}
```
### 更新字符串
字符串可以增加大小并更改其内容,就像 Vec<T> 一样,如果你向其中添加更多数据。此外,还可以方便地使用 + 运算符或 format! 宏来连接字符串值。
#### push_str和push附加到字符串
分别用于字符串切片和字符。都不会获得所有权(因为是引用)。
![image-20230116151812443](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230116151812443.png)
当使用字符串时,需取切片
![image-20230116152054012](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230116152054012.png)
### 字符串拼接
#### +运算符和format!宏连接
```
+ 运算符使用以下add方法
fn add(self, s: &str) -> String {
```
在调用add时使用&s2是因为编译器可以将**&String参数强制转换为&str**。当我们调用add方法时,Rust使用deref强制转换。
![image-20230116161123068](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230116161123068.png)
使用+操作符会失去第一个参数的所有权,同时在处理多个字符串拼接时会特别笨拙。以下为使用format!宏进行拼接,不会失去所有权(使用引用)。
![image-20230116162822236](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230116162822236.png)
### 字符串索引
**Rust 字符串不支持索引、**
![image-20230116163655653](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230116163655653.png)
#### 内部表示
一个字符串是 Vec<u8>的封装。当以 UTF-8 编码时,这些字母中的每一个都占用 1 个字节。但当以 Unicode 编码时,每一项占用两个字节。**字符串字节的索引并不总是与有效的 Unicode 标量值相关联。** UTF-8 是一种将 Unicode 编码转换成字节序列的转换格式。
![image-20230116165523344](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230116165523344.png)
因为存储的单元字节大小不同,故而直接通过下标取值有时会取到错误值。为了避免返回意外值并导致可能无法立即发现的错误,Rust 根本不编译此代码,并在开发过程的早期防止误解。
#### 字节和标量值和字形群
关于 UTF-8 的另一点是,从 Rust 的角度来看,实际上有三种相关的方式来查看字符串:作为字节、标量值和字素簇(最接近我们所说的*字母*)。
印地语单词“नमस”,它存储为一个`u8`值向量,如下所示:
```
[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164,
224, 165, 135]
```
18个字节,这就是计算机最终存储此数据的方式。如果我们将它们视为 Unicode 标量值,这就是 Rust 的`char`类型,那么这些字节如下所示:
```
['न', 'म', 'स', '', 'त', '']
```
这里有六个`char`值,但第四个和第六个不是字母:它们是本身没有意义的变音符号。最后,如果我们将它们视为字素簇,我们会得到人们所说的构成印地语单词的四个字母:
```
["न", "म", "स", "त"]
```
Rust 提供了不同的方式来解释计算机存储的原始字符串数据,这样每个程序都可以选择它需要的解释,而不管数据使用的是哪种人类语言。
Rust 不允许我们通过索引 a`String`来获取字符的最后一个原因是索引操作预计总是需要常数时间 (O(1))。但是不能保证 a 的性能`String`,因为 Rust 必须遍历从头到索引的内容以确定有多少个有效字符。
### 字符串切片
可以使用索引创建字符串切片,但需谨慎,会产生意外panic。
![image-20230129100107046](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129100107046.png)
当对二个字节字符取一个字节时,会产生如下错误:
![image-20230129100347688](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129100347688.png)
### 迭代字符串
`chars`方法返回每个字符,`bytes`方法返回每个原始字节。
**有效的 Unicode 标量值可能由超过 1 个字节组成。**
![image-20230129103709654](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129103709654.png)
## HashMap<K,V>
### 创建HashMap
首先从标准库引用HashMap。哈希映射将其数据存储在堆上。哈希映射是同类型的: 所有键必须具有相同的类型,所有值也必须具有相同的类型。
![image-20230129105734858](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129105734858.png)
### 访问HashMap
get方法返回一个Option<&V>。如果哈希映射中该键没有值,`get`将返回`None`。
`unwrap_or()` 函数是 Rust 中 `Option` 类型的一个常用函数,它用于在值为 `None` 时返回一个默认值。它接受一个参数,在 `Option` 类型为 `None` 时返回该参数。
![image-20230129111116825](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129111116825.png)
#### 遍历HashMap
以任意顺序打印每一对。
![image-20230129112257955](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129112257955.png)
### HashMap所有权
对于实现`Copy`特征的类型,如`i32`,值被复制到哈希映射中。对于拥有的值,如`String`,这些值将被移动,哈希映射将成为这些值的所有者。
![image-20230129113639017](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129113639017.png)
#### 存储字符串引用(保留所有权)
![image-20230129114217635](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129114217635.png)
但是可能会出现这种情况:
![image-20230129115000375](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129115000375.png)
推荐使用clone:
![image-20230129115130528](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129115130528.png)
### 更新HashMap的值
1. 覆盖,插入了两个相同key
![image-20230129132045261](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129132045261.png)
2. 仅在键不存在时添加**键和值**。
如果该键确实存在于哈希映射中,则现有值应保持原样。如果键不存在,则插入它并为其赋值。`entry`将您要检查的密钥作为参数。该`entry`方法的返回值是一个枚举`Entry`,代表一个可能存在也可能不存在的值。
`or_insert` 方法被定义为如果该键存在,则`Entry`返回对相应`Entry`键值的可变引用,如果不存在,则将参数作为该键的新值插入,并返回对新值的可变引用。
![image-20230129132647150](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129132647150.png)
3. 基于旧值更新值
split_whitespace方法返回 中值的子片的迭代器,由空格分隔text。该`or_insert`方法返回一个可变引用 ( `&mut V`) 到指定键的值。这里我们将可变引用存储在`count`变量中,因此为了分配给该值,我们必须首先`count`使用星号 (`*`) 取消引用。
![image-20230129134200025](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129134200025.png)
### 哈希函数
默认情况下,HashMap使用一种名为SipHash的哈希函数,可以提供对哈希表的拒绝服务(DoS)攻击的抵抗力。这不是最快的哈希算法,但是为了更好的安全性而降低性能的权衡是值得的。如果您对代码进行了分析并发现默认哈希函数对您的目的太慢,您可以通过指定其他函数来切换。哈希器是一种实现BuildHasher特征的类型。

@ -0,0 +1,113 @@
# To panic! or Not to panic!
什么使用返回panic?什么时候Result(类似 try-catch)?
在示例、原型代码和测试等情况下,更合适的做法是编写具有panic的代码,而不是返回Result。
个人认为,开发时除了能控制逻辑的代码可以多返回panic。而发布产品时尽量不返回panic,因为会导致程序停止(健壮性低)。
## panic的各种情况
1. 示例、原型代码和测试时,使用panic。
2. 比编译器掌握更多信息的情况,可以使用Result,结果可控。但如果IP地址字符串来自用户而不是硬编码到程序中,因此*确实*有失败的可能性,我们肯定希望以`Result`更稳健的方式处理。.
![image-20230208225653099](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230208225653099.png)
## 错误处理指南
当代码可能最终处于糟糕的状态时,让代码返回panic是明智的。
如果有人调用您的代码并传入了没有意义的值,最好返回一个错误,以便库的用户可以决定在这种情况下他们想要做什么。然而,如果继续下去可能是不安全的或有害的,最好的选择可能是呼叫恐慌!并提醒使用您的库的人注意他们代码中的错误,以便他们在开发过程中修复它。同样,恐慌!如果您正在调用超出您控制的外部代码,并且它返回您无法修复的无效状态,则通常适用。
然而,当预期失败时,返回Result比制造恐慌更合适!调用。例如,向解析器提供格式错误的数据或HTTP请求返回指示您已达到速率限制的状态。在这些情况下,返回Result表明失败是调用代码必须决定如何处理的预期可能性。
## 创建用于验证的自定义类型
猜数字旧版代码: 如果程序只对 1 到 100 之间的值进行操作是绝对关键的,并且它有许多满足此要求的函数,那么在每个函数中进行这样的检查将是乏味的(并且可能会影响表现)。
```rust
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
loop {
// --snip--
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: i32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
if guess < 1 || guess > 100 {
println!("The secret number will be between 1 and 100.");
continue;
}
match guess.cmp(&secret_number) {
// --snip--
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
}
}
```
更好的方式:
```rust
#![allow(unused)]
fn main() {
pub struct Guess {
value: i32,
}
impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 || value > 100 {
panic!("Guess value must be between 1 and 100, got {}.", value);
}
Guess { value }
}
pub fn value(&self) -> i32 {
self.value
}
}
}
```
运行图:
![image-20230209001139397](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230209001139397.png)
## 总结
Rust的错误处理特性旨在帮助您编写更健壮的代码。panic!宏信号表明您的程序处于无法处理的状态,并允许您告诉进程停止,而不是试图继续处理无效或不正确的值。Result枚举使用Rust的类型系统来指示操作可能失败,而您的代码可以从中恢复。您可以使用Result告诉调用您的代码的代码,它也需要处理潜在的成功或失败。使用panic!和Result在适当的情况下将使您的代码在面对不可避免的问题时更加可靠。

@ -0,0 +1,169 @@
# Error Handling
Rust将错误分为两大类:**可恢复**和**不可恢复**的错误。对于可恢复的错误,例如找不到文件错误,我们可能只需要向用户报告问题并重试操作。不可恢复的错误总是 bug 的症状,例如试图访问数组末尾之外的位置,所以我们要立即停止程序。
大多数语言不区分这两种错误,并使用类似于异常的机制来处理。但是Rust没有异常,它使用类型Result<T, E>来处理可恢复的错误,使用 panic! 宏来停止程序执行当遇到不可恢复的错误。本章将先讨论调用 panic! ,然后讨论返回Result<T, E>的值。此外,我们还将探讨在决定是恢复错误还是停止执行时的考虑因素。
## panic
当发生恐慌时,程序开始展开,这意味着Rust向上走回堆栈并清理每个函数遇到的数据。然而,这种向上走和清理是很多工作。因此,Rust允许您选择立即中止的替代方法,这样可以在不清理的情况下结束程序。
```rust
// 发布模式下出现 panic 时中止。 修改Cargo.toml文件
[profile.release]
panic = 'abort'
```
### 简单的panic!
![image-20230129145332940](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129145332940.png)
第一行显示了我们的恐慌消息以及我们源代码中发生恐慌的位置:*src/main.rs:2:5*表示它是第二行,我们的*src/main.rs*文件的第五个字符。
### 使用panic!回溯
访问越界
![image-20230129150209627](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129150209627.png)
根据错误提示。设置`RUST_BACKTRACE` 环境变量以获取导致错误的确切原因的回溯。
![image-20230129151706140](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129151706140.png)
## 可恢复的错误Result
枚举Result的定义:
````rust
enum Result<T, E> {
Ok(T),
Err(E),
}
````
打开不存在的文件:
![image-20230129160739718](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129160739718.png)
创建文件后:
![image-20230129161343548](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129161343548.png)
## 匹配不同的错误
通过代码定位到具体的错误,而非自己去查找、
![image-20230129162635694](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230129162635694.png)
#### 不使用match
`unwrap_or()`方法接受一个默认值作为参数,如果Result的值是Ok,则返回其内部的值,如果Result的值是Err,则返回默认值。这个方法适用于错误情况下提供一个固定的默认值的情况。
`unwrap_or_else()`方法接受一个回调函数作为参数,如果Result的值是Ok,则返回其内部的值,如果Result的值是Err,则调用传递给unwrap_or_else的回调函数,并返回其返回值。回调函数允许对错误情况进行自定义处理。
![image-20230130092333945](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230130092333945.png)
## unwrap and expect
`unwrap()` 是一种方法,它返回包含的值或在错误时引发错误。它用于您确信结果将是“Ok”的情况,并希望如果不是则崩溃程序。
![image-20230130100535038](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230130100535038.png)
使用 expect,允许我们选择`panic!`错误消息。相较于unwrap,**expect使用的更多**。
![image-20230130100857207](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230130100857207.png)
## 传播错误
当一个函数的实现调用可能失败的操作时,与其在函数内部处理错误,您可以将错误返回给调用代码,以便它可以决定如何处理。这称为传播错误,并给调用代码提供了更多的控制权,因为在调用代码中可能有更多的信息或逻辑来决定如何处理错误,而您在代码的上下文中可能不具备这些信息或逻辑。
![image-20230130114134605](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230130114134605.png)
运行(包含数据):
![image-20230130153825415](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230130153825415.png)
运行(不存在文件):
![image-20230130154105950](C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230130154105950.png)
此函数可以以更短的方式编写,但是我们首先要手动执行很多操作,以探索错误处理。
## ?运算符--传播错误快捷方式
以下代码同前文代码作用完全相同
![image-20230206224411662](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230206224411662.png)
### 使用立即链接进一步精简代码
![image-20230206225432532](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230206225432532.png)
### 使用系统函数(fs::read_to_string)
![image-20230206230153864](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230206230153864.png)
## ?可以出现的位置
该`?`运算符只能用在返回类型与所用值**兼容**的函数,若尝试在main函数(无返回值时)中使用`?`是不可以的。
![image-20230206231105650](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230206231105650.png)
### 使用?with option<T>
在给定文本中查找第一行的最后一个字符:
![image-20230206232023649](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230206232023649.png)
如果`text`是空字符串,则此调用`next`将返回`None`,在这种情况下我们使用`?`停止并`None`从 返回`last_char_of_first_line`。如果`text`不是空字符串,`next`将返回一个`Some`包含 中第一行的字符串切片的值`text`。
`chars`字符串切片以获取其字符的迭代器。调用`last`以返回迭代器中的最后一项。
## 修改main函数来适配?
![image-20230206233206129](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230206233206129.png)
`Box<dyn Error>`类型是一个*特征对象*, 目前可以理解为“任何类型的错误”。`?`允许在具有错误类型的函数中使用`Result` 值,因为它允许提前返回任何值。即使此函数的主体将只返回 类型的错误,通过指定,即使返回其他错误的更多代码添加到 的主体,此签名也将继续正确。
当main函数返回Result<(), E>时,如果main返回Ok(()),可执行文件将以0值退出,如果main返回Err值,则将以非0值退出。C语言编写的可执行文件在退出时返回整数:成功退出的程序返回整数0,错误退出的程序返回非0的整数。Rust还从可执行文件中返回整数以与此约定兼容。
main函数可以返回任何实现std::process:: terminate特征的类型,该特征包含一个返回ExitCode的函数报告。有关为自己的类型实现terminate特征的更多信息,参阅标准库[文档](https://doc.rust-lang.org/std/process/trait.Termination.html)。

@ -0,0 +1,410 @@
在这种情况下,使用闭包和使用函数来实现同样的功能是没有本质区别的,因为它们都可以创建一个新的空向量作为默认值。
但是,闭包和函数的实现方式可能会在其他情况下产生不同的行为或效果。例如,如果我们需要捕获环境中的变量来生成默认值,那么只有闭包才能实现这个功能。此外,闭包还可以使用 move 语义来强制获取环境中的变量的所有权,从而避免变量的所有权问题。
另外,使用函数可能会更加直观和简洁,因为它不需要包含闭包的语法和限制。在一些情况下,使用函数可以使代码更加易于阅读和理解。不过,在需要捕获环境中的变量或需要在闭包内执行一些特定操作时,使用闭包可能会更加合适。
![img](data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20version=%271.1%27%20width=%2730%27%20height=%2730%27/%3e)![kyushuansuoru@gmail.com](https://chat.openai.com/_next/image?url=https%3A%2F%2Fs.gravatar.com%2Favatar%2Ff25d9c095cc7a9d7f7f370fdd73cab4e%3Fs%3D480%26r%3Dpg%26d%3Dhttps%253A%252F%252Fcdn.auth0.com%252Favatars%252Fky.png&w=64&q=75)
能否举个代码例子
当需要捕获环境中的变量或需要在闭包内执行一些特定操作时,使用闭包是更好的选择。下面是一个例子,假设我们有一个向量 `vec`,我们需要创建一个函数,该函数将该向量的所有元素乘以某个系数 `factor`
```
rustCopy codelet vec = vec![1, 2, 3, 4, 5];
let factor = 2;
// 使用闭包来实现
let mut result = Vec::new();
vec.iter().for_each(|&x| result.push(x * factor));
println!("Result using closure: {:?}", result);
// 使用函数来实现
fn multiply_by_factor(v: &Vec<i32>, f: i32) -> Vec<i32> {
let mut result = Vec::new();
v.iter().for_each(|&x| result.push(x * f));
result
}
let result = multiply_by_factor(&vec, factor);
println!("Result using function: {:?}", result);
```
在上面的例子中,我们使用闭包和函数来实现同样的功能,即将向量 `vec` 中的所有元素乘以系数 `factor`,并返回一个新的向量。使用闭包时,我们可以捕获外部变量 `factor`,从而在闭包内使用它;而使用函数时,则需要将 `factor` 作为函数的参数进行传递。
在这种情况下,使用闭包和使用函数的效果是相同的,但是使用闭包可以使代码更加简洁,而使用函数则可以更加明确地指定函数的参数和返回值类型。
![img](data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20version=%271.1%27%20width=%2730%27%20height=%2730%27/%3e)![kyushuansuoru@gmail.com](https://chat.openai.com/_next/image?url=https%3A%2F%2Fs.gravatar.com%2Favatar%2Ff25d9c095cc7a9d7f7f370fdd73cab4e%3Fs%3D480%26r%3Dpg%26d%3Dhttps%253A%252F%252Fcdn.auth0.com%252Favatars%252Fky.png&w=64&q=75)
当不需要捕获内容时,就比如创建一个变量,二者举个例子
当不需要捕获环境中的变量时,使用函数和闭包实现同样的功能也是没有本质区别的。下面是一个例子,假设我们需要创建一个新的空向量:
```
rustCopy code// 使用闭包来实现
let new_vec = || Vec::new();
let vec1 = new_vec();
let vec2 = new_vec();
println!("Result using closure: {:?}, {:?}", vec1, vec2);
// 使用函数来实现
fn new_vector() -> Vec<i32> {
Vec::new()
}
let vec1 = new_vector();
let vec2 = new_vector();
println!("Result using function: {:?}, {:?}", vec1, vec2);
```
在上面的例子中,我们使用闭包和函数来创建一个新的空向量,并分别将其赋值给变量 `vec1``vec2`。使用闭包时,我们创建了一个无参的闭包 `new_vec`,该闭包返回一个新的空向量。而使用函数时,则直接定义了一个名为 `new_vector` 的函数来返回一个新的空向量。
在这种情况下,使用闭包和使用函数的效果是相同的,并且使用函数可能会更加简洁和直观。
![img](data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20version=%271.1%27%20width=%2730%27%20height=%2730%27/%3e)![kyushuansuoru@gmail.com](https://chat.openai.com/_next/image?url=https%3A%2F%2Fs.gravatar.com%2Favatar%2Ff25d9c095cc7a9d7f7f370fdd73cab4e%3Fs%3D480%26r%3Dpg%26d%3Dhttps%253A%252F%252Fcdn.auth0.com%252Favatars%252Fky.png&w=64&q=75)
let opt_vec: Option<Vec<i32>> = None; let default_vec = Vec::new(); // 创建一个空 Vec<i32> let vec = opt_vec.unwrap_or_else(|| default_vec); println!("{:?}", vec); // 输出:[] 这个是通过闭包吗
是的,这个例子中使用了闭包来实现 `unwrap_or_else` 方法。具体来说,当 `opt_vec``None` 时,该方法会调用一个闭包来生成一个默认值。在这个例子中,我们定义了一个名为 `default_vec` 的空向量,并将其作为闭包的默认值。如果 `opt_vec``None`,则该闭包会返回 `default_vec`,否则它会返回 `opt_vec` 中包含的向量。
在这个例子中,我们使用了一个无参的闭包 `|| default_vec`,其中 `||` 表示该闭包不需要任何参数,而 `default_vec` 则是该闭包的返回值。这个闭包的作用是返回默认的空向量,它会在 `unwrap_or_else` 方法需要时被调用。
使用闭包来实现 `unwrap_or_else` 方法的好处是,它可以更灵活地生成默认值。例如,我们可以使用一个动态生成的向量作为默认值,而不是使用一个静态定义的空向量。
![img](data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20version=%271.1%27%20width=%2730%27%20height=%2730%27/%3e)![kyushuansuoru@gmail.com](https://chat.openai.com/_next/image?url=https%3A%2F%2Fs.gravatar.com%2Favatar%2Ff25d9c095cc7a9d7f7f370fdd73cab4e%3Fs%3D480%26r%3Dpg%26d%3Dhttps%253A%252F%252Fcdn.auth0.com%252Favatars%252Fky.png&w=64&q=75)
这个如何改为通过函数
要使用函数来代替闭包,我们需要将返回默认值的逻辑封装在一个函数中。下面是一个使用函数来实现同样功能的例子:
```
rustCopy codelet opt_vec: Option<Vec<i32>> = None;
fn default_vec() -> Vec<i32> {
Vec::new()
}
let vec = opt_vec.unwrap_or_else(default_vec);
println!("{:?}", vec); // 输出:[]
```
在这个例子中,我们定义了一个名为 `default_vec` 的函数来返回默认的空向量。函数没有参数,并且返回一个 `Vec<i32>`。然后,我们将该函数作为参数传递给 `unwrap_or_else` 方法,以便在需要默认值时调用它。
值得注意的是,在这个例子中,我们不需要使用闭包来实现同样的功能,因为函数本身就可以作为一个可调用的对象来传递给 `unwrap_or_else` 方法。
![img](data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20version=%271.1%27%20width=%2730%27%20height=%2730%27/%3e)![kyushuansuoru@gmail.com](https://chat.openai.com/_next/image?url=https%3A%2F%2Fs.gravatar.com%2Favatar%2Ff25d9c095cc7a9d7f7f370fdd73cab4e%3Fs%3D480%26r%3Dpg%26d%3Dhttps%253A%252F%252Fcdn.auth0.com%252Favatars%252Fky.png&w=64&q=75)
我们不需要使用闭包来实现同样的功能,因为函数本身就可以作为一个可调用的对象来传递给 unwrap_or_else 方法。 什么意思
在 Rust 中,函数也可以像闭包一样被调用。这意味着,当我们需要一个可调用的对象时,函数可以作为一个选项来使用。与闭包不同的是,函数不需要捕获环境中的值,因此函数的使用方式可以更加简单和直接。
在上面的例子中,我们使用了一个函数 `default_vec()` 来返回默认的空向量。我们可以将该函数作为一个可调用的对象,直接传递给 `unwrap_or_else` 方法,作为一个参数来使用。这个方法将在需要默认值时,直接调用该函数,返回生成的默认值。
总之,使用函数来替代闭包的好处是,函数的使用方式更加直接和简单,而且可以方便地重用和测试。而闭包通常用于需要捕获环境中的值的情况下,因为它可以直接访问环境中的变量和状态。
![img](data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20version=%271.1%27%20width=%2730%27%20height=%2730%27/%3e)![kyushuansuoru@gmail.com](https://chat.openai.com/_next/image?url=https%3A%2F%2Fs.gravatar.com%2Favatar%2Ff25d9c095cc7a9d7f7f370fdd73cab4e%3Fs%3D480%26r%3Dpg%26d%3Dhttps%253A%252F%252Fcdn.auth0.com%252Favatars%252Fky.png&w=64&q=75)
捕获环境的值,是指操作内容吗
捕获环境的值是指闭包在定义时可以访问其所在环境中的变量和状态。具体来说,如果一个闭包捕获了一个变量,那么在闭包被调用时,它将可以访问和修改该变量。这是因为闭包在创建时会将所需的变量捕获到自己的内部环境中,并在调用时使用这些变量的当前值。
因此,如果我们需要访问或修改环境中的变量或状态,那么使用闭包是一个好的选择。而如果我们只需要一个函数来执行一些操作,并且不需要访问或修改环境中的变量或状态,那么使用函数来代替闭包可以使代码更加简单和直接。
![img](data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20version=%271.1%27%20width=%2730%27%20height=%2730%27/%3e)![kyushuansuoru@gmail.com](https://chat.openai.com/_next/image?url=https%3A%2F%2Fs.gravatar.com%2Favatar%2Ff25d9c095cc7a9d7f7f370fdd73cab4e%3Fs%3D480%26r%3Dpg%26d%3Dhttps%253A%252F%252Fcdn.auth0.com%252Favatars%252Fky.png&w=64&q=75)
不是也有不捕获的闭包吗
是的,闭包也可以不捕获环境中的变量或状态。这种闭包被称为 "Fn" 闭包,它可以作为一个纯函数来使用,不依赖于环境中的任何变量或状态。
在 Rust 中,闭包有三种类型:Fn、FnMut 和 FnOnce,它们分别表示不可变引用、可变引用和所有权的不同所有权和可变性组合。如果我们只需要一个不依赖于环境的纯函数,那么我们可以使用 Fn 闭包来实现,而不需要使用捕获环境的闭包。
总之,当我们需要一个可调用的对象来执行一些操作时,函数和不捕获的闭包都是一种很好的选择。使用函数通常更加简单和直接,而不捕获的闭包则可以提供一些更加灵活的行为,例如允许在闭包内部访问和修改环境中的变量。
![img](data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20version=%271.1%27%20width=%2730%27%20height=%2730%27/%3e)![kyushuansuoru@gmail.com](https://chat.openai.com/_next/image?url=https%3A%2F%2Fs.gravatar.com%2Favatar%2Ff25d9c095cc7a9d7f7f370fdd73cab4e%3Fs%3D480%26r%3Dpg%26d%3Dhttps%253A%252F%252Fcdn.auth0.com%252Favatars%252Fky.png&w=64&q=75)
闭包实现了三个特质(trait)之一:Fn、FnMut、FnOnce 。函数也可以实现吗
2 / 2
是的,函数也可以实现这三个 trait 中的任何一个。
在 Rust 中,函数也被视为一种闭包,它们可以被转换为一个实现了 Fn、FnMut 或 FnOnce 的闭包对象。这意味着我们可以使用函数来代替闭包,只要函数的签名与所需的 trait 相匹配即可。
例如,如果我们需要一个实现了 FnMut 特质的闭包,我们可以使用一个可变的函数指针,例如:
```
rustCopy codefn add_one(x: &mut i32) {
*x += 1;
}
let mut closure = add_one as fn(&mut i32);
let mut x = 0;
closure(&mut x);
assert_eq!(x, 1);
```
在这个例子中,我们使用一个可变的函数指针 `add_one as fn(&mut i32)` 来创建一个实现了 FnMut 特质的闭包。这个闭包可以接受一个可变的 `i32` 引用,并将它的值加 1。
总之,函数可以被视为一种特殊的闭包,它们可以被转换为任何实现了 Fn、FnMut 或 FnOnce 特质的闭包对象。
![img](data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20version=%271.1%27%20width=%2730%27%20height=%2730%27/%3e)![kyushuansuoru@gmail.com](https://chat.openai.com/_next/image?url=https%3A%2F%2Fs.gravatar.com%2Favatar%2Ff25d9c095cc7a9d7f7f370fdd73cab4e%3Fs%3D480%26r%3Dpg%26d%3Dhttps%253A%252F%252Fcdn.auth0.com%252Favatars%252Fky.png&w=64&q=75)
函数实现了哪个特征
函数可以实现三个特质之一:Fn、FnMut 和 FnOnce。
具体来说,函数的实现取决于函数的签名和它在代码中的使用方式。如果一个函数不需要修改它所引用的变量,那么它可以实现 Fn 特质;如果函数需要修改它所引用的变量,则可以实现 FnMut 特质;如果函数需要获取它所引用的变量的所有权,则可以实现 FnOnce 特质。
例如,以下代码演示了如何使用函数来实现 Fn 特质:
```
rustCopy codefn add(x: i32, y: i32) -> i32 {
x + y
}
let f: fn(i32, i32) -> i32 = add;
let result = f(1, 2);
assert_eq!(result, 3);
```
在这个例子中,我们定义了一个函数 `add`,它接受两个 `i32` 参数并返回它们的和。然后,我们使用一个函数指针 `f` 来捕获这个函数,并将其传递给一个需要实现 Fn 特质的方法。
请注意,我们在定义函数指针时使用了 `fn(i32, i32) -> i32` 类型签名,这与 `add` 函数的签名相匹配。因此,我们可以将 `add` 函数视为一个实现了 Fn 特质的闭包对象,并将它传递给任何需要 Fn 特质的方法。
类似地,我们可以使用其他函数签名来实现 FnMut 或 FnOnce 特质,具体取决于函数需要的权限和所有权。
![img](data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20version=%271.1%27%20width=%2730%27%20height=%2730%27/%3e)![kyushuansuoru@gmail.com](https://chat.openai.com/_next/image?url=https%3A%2F%2Fs.gravatar.com%2Favatar%2Ff25d9c095cc7a9d7f7f370fdd73cab4e%3Fs%3D480%26r%3Dpg%26d%3Dhttps%253A%252F%252Fcdn.auth0.com%252Favatars%252Fky.png&w=64&q=75)
他是自己实现的吗,需要配置什么嘛
不需要配置任何东西,Rust 的标准库已经包含了 Fn、FnMut 和 FnOnce 特质的默认实现。只要你的函数签名符合这些特质的要求,你的函数就可以自动实现这些特质。因此,你可以直接使用函数作为可调用对象来传递给需要实现这些特质的方法。
需要注意的是,如果你想在函数中修改捕获的变量,那么这个函数必须实现 FnMut 特质。类似地,如果你想获取捕获变量的所有权,那么这个函数必须实现 FnOnce 特质。否则,你可以使用 Fn 特质来实现这个函数。
例如,以下代码演示了如何使用一个函数实现 FnMut 特质:
```
rustCopy codefn main() {
let mut counter = 0;
let inc = || counter += 1;
apply_twice(inc);
println!("counter = {}", counter); // 输出:counter = 2
}
fn apply_twice<F>(f: F)
where
F: FnMut(),
{
f();
f();
}
```
在这个例子中,我们定义了一个函数 `apply_twice`,它接受一个实现了 FnMut 特质的可调用对象,并将其应用两次。然后,我们定义了一个捕获了外部变量 `counter` 的闭包 `inc`,并将其传递给 `apply_twice` 函数。由于 `inc` 实现了 FnMut 特质,因此它可以被传递给 `apply_twice` 函数并被成功调用。
请注意,我们需要将 `apply_twice` 函数的参数 `f` 声明为 `F: FnMut()`,这表示它需要一个可调用对象,这个对象可以修改它所捕获的变量(在这个例子中是 `counter`)。由于 `inc` 闭包满足这个要求,因此它可以被传递给 `apply_twice` 函数,并在其中被成功调用两次。最终,我们打印出 `counter` 的值,它应该等于 2。
![img](data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20version=%271.1%27%20width=%2730%27%20height=%2730%27/%3e)![kyushuansuoru@gmail.com](https://chat.openai.com/_next/image?url=https%3A%2F%2Fs.gravatar.com%2Favatar%2Ff25d9c095cc7a9d7f7f370fdd73cab4e%3Fs%3D480%26r%3Dpg%26d%3Dhttps%253A%252F%252Fcdn.auth0.com%252Favatars%252Fky.png&w=64&q=75)
fnonce是做什么
`fnonce` 是一个 Rust crate(包),它提供了一个宏,用于将普通函数转换为实现 `FnOnce` 特质的闭包。因为普通函数默认不实现 `FnOnce` 特质,所以在一些需要传递闭包的情况下,可以使用 `fnonce` 宏来将普通函数转换为实现 `FnOnce` 特质的闭包,从而避免了手动实现闭包的麻烦。
![img](data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20version=%271.1%27%20width=%2730%27%20height=%2730%27/%3e)![kyushuansuoru@gmail.com](https://chat.openai.com/_next/image?url=https%3A%2F%2Fs.gravatar.com%2Favatar%2Ff25d9c095cc7a9d7f7f370fdd73cab4e%3Fs%3D480%26r%3Dpg%26d%3Dhttps%253A%252F%252Fcdn.auth0.com%252Favatars%252Fky.png&w=64&q=75)
它的作用是什么
`fnonce` 的主要作用是将普通函数转换为实现 `FnOnce` 特质的闭包。这个特质在 Rust 中被用于表示能够被调用一次且不需要借用环境变量的闭包。使用 `fnonce` 宏可以方便地将普通函数转换为这种类型的闭包,从而可以在需要这种类型的闭包的地方使用普通函数。
![img](data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20version=%271.1%27%20width=%2730%27%20height=%2730%27/%3e)![kyushuansuoru@gmail.com](https://chat.openai.com/_next/image?url=https%3A%2F%2Fs.gravatar.com%2Favatar%2Ff25d9c095cc7a9d7f7f370fdd73cab4e%3Fs%3D480%26r%3Dpg%26d%3Dhttps%253A%252F%252Fcdn.auth0.com%252Favatars%252Fky.png&w=64&q=75)
fnmut作用是修改内容,fnonce呢
`FnOnce` 特质表示的是可以被调用一次且不需要借用环境变量的闭包。因此,使用实现 `FnOnce` 特质的闭包,可以将闭包所占用的环境变量移动(move)到闭包内部,从而避免了在闭包中对环境变量进行可变借用或不可变借用的情况。
`fnonce` 这个 crate(包)提供的宏可以将一个普通函数转换成实现 `FnOnce` 特质的闭包,使得这个函数可以被当作闭包使用,并且可以将函数的参数移动(move)到闭包内部,从而可以在需要实现 `FnOnce` 特质的场景下,方便地使用普通函数。
![img](data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20version=%271.1%27%20width=%2730%27%20height=%2730%27/%3e)![kyushuansuoru@gmail.com](https://chat.openai.com/_next/image?url=https%3A%2F%2Fs.gravatar.com%2Favatar%2Ff25d9c095cc7a9d7f7f370fdd73cab4e%3Fs%3D480%26r%3Dpg%26d%3Dhttps%253A%252F%252Fcdn.auth0.com%252Favatars%252Fky.png&w=64&q=75)
fn呢?
`fn` 关键字用于定义一个函数,函数是一段独立的代码块,可以被其他代码调用。与闭包不同,函数不占用环境变量,也不需要借用任何变量。因此,函数不需要实现 `Fn`、`FnMut` 或 `FnOnce` 特质,它们已经具备被调用的能力。
在 Rust 中,函数和闭包都是可调用对象,因此它们可以在很多相同的场景下使用。但是,闭包相对于函数有更多的灵活性,例如可以捕获环境变量、支持泛型、可以被转移等。因此,当需要在闭包中操作环境变量或者使用其它闭包特性时,闭包更适合使用。而当我们只需要一个简单的函数来执行一些特定的操作时,函数则是更加方便和高效的选择。
![img](data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20version=%271.1%27%20width=%2730%27%20height=%2730%27/%3e)![kyushuansuoru@gmail.com](https://chat.openai.com/_next/image?url=https%3A%2F%2Fs.gravatar.com%2Favatar%2Ff25d9c095cc7a9d7f7f370fdd73cab4e%3Fs%3D480%26r%3Dpg%26d%3Dhttps%253A%252F%252Fcdn.auth0.com%252Favatars%252Fky.png&w=64&q=75)
fn不是也是一个trait吗
`fn` 关键字不是 trait,而是 Rust 中定义函数的关键字。与 `Fn`、`FnMut` 和 `FnOnce` 不同,`fn` 不需
![img](data:image/svg+xml,%3csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20version=%271.1%27%20width=%2730%27%20height=%2730%27/%3e)![kyushuansuoru@gmail.com](https://chat.openai.com/_next/image?url=https%3A%2F%2Fs.gravatar.com%2Favatar%2Ff25d9c095cc7a9d7f7f370fdd73cab4e%3Fs%3D480%26r%3Dpg%26d%3Dhttps%253A%252F%252Fcdn.auth0.com%252Favatars%252Fky.png&w=64&q=75)
闭包实现了三个特质(trait)之一:Fn、FnMut、FnOnce, 这三个特征分别是什么作用
这三个特质(trait)都是 Rust 中闭包(closure)类型的一部分,它们的作用分别是:
- `Fn` 特质描述了一个闭包可以通过引用捕获外部变量的能力,这种捕获方式被称为“不可变借用捕获”,意味着闭包可以读取外部变量但不能修改它们。`Fn` 特质的闭包可以在其环境不可变时被调用,例如一个只读数据结构。
- `FnMut` 特质描述了一个闭包可以通过可变借用捕获外部变量的能力,这意味着闭包可以读取和修改它们。`FnMut` 特质的闭包可以在其环境可变时被调用,例如一个可变数据结构。
- `FnOnce` 特质描述了一个闭包可以通过移动语义(move semantics)将所有捕获的外部变量拥有并取走的能力,这意味着闭包可以获取它捕获的变量的所有权并对它们进行任意操作。`FnOnce` 特质的闭包只能被调用一次,之后它将拥有并占用它捕获的变量,不能再被调用。
Rust 的闭包可以实现这三个特质中的一个或多个,具体取决于闭包捕获外部变量的方式。通常情况下,如果闭包需要修改或获取其捕获变量的所有权,它们将会实现 `FnMut``FnOnce` 特质。如果闭包只需要读取其捕获变量,它将会实现 `Fn` 特质。

@ -0,0 +1,7 @@
![image-20230320100448366](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230320100448366.png)
![image-20230320100434331](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230320100434331.png)

@ -0,0 +1,67 @@
# 波特率
### 基础知识
1. 串口传输的波特率即为每秒钟传输**二进制**的位数。
2. 系统时钟50M(为串口提供时钟的时钟频率),波特率115200。 (<u>不是很理解,定义?</u>
| 因 | 国 |
| ------------------------------------------------------------ | --------------------------------------- |
| 系统时钟(为串口提供时钟的时钟频率)-50M | 时钟周期 1/(50*10^6) |
| 一个时钟周期可以计数1次 | 50M时钟1s计数50,000,000个 |
| 系统时钟-50M | 计1个数需要1/(50*10^6)S |
| 波特率-115200 | 1s传输二进制的位数115200bit |
| 波特率-115200 | 传输1bit需要1/115200s=8.68us |
| 50M系统时钟----波特率为115200 | 传输1bit需要计数(50*10^6)/115200 = 434 |
| 以上重点是推导出50M系统时钟—波特率为115200条件下传输1bit需要计数个数为434。 | |
### 串口传输格式
常用的串口传输格式:1bit起始位+8bit数据位+1bit停止位(无奇偶校验位):
![image-20230227155411200](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230227155411200.png)
所以传输1Byte数据串口需要传输10bit数据。上面计算得传输1bit需要的时间为8.68us,则传输1Byte需要时间为8.68*10=86.8us。
### 波特率
图中描述了1Byte数据传输的示意图:
![image-20230227160244608](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230227160244608.png)
#### 什么时候去采样串口线上的数据呢?
★观察上图,Buad_Flag信号(通道2)表示了传输1Bit传输的间隔,每遇到1个Buad_Flag=1的信号,数据线上切换1次数据,所以两个Buad_Flag=1之间的数据是稳定的数据,根据抽样定理是不是应该在两个Buad_Flag=1信号的中间去采样数据呢,其实就是在1bit数据持续期间的中间点采样,才能得到最稳定的数据。
![image-20230227161319540](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\image-20230227161319540.png)
★图中序号①-⑩分别为10bit数据的采样点,采样点处提取数据为0101_0101(0x55),低位在前。
★★★至此,从下往上再看一遍,就明白了我们所熟悉的波特率(115200/9600/…)怎么来的。
☞然后再想想为什么51单片机会有一个定时器专门服务于串口。
☞再想想为什么9600波特率和115200波特率不能对接了,原理很简单我这边2秒发一次数据,你那边1秒接一次数据,怎么能成功接收呢。
收发波特率一致:![2021052511091847](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\2021052511091847.gif)
收发波特率不一致,导致RX端不能正常接收:
![20210525110925941](C:\Users\10074\AppData\Roaming\Typora\typora-user-images\20210525110925941.gif)
[原文地址](https://blog.csdn.net/qq_40147893/article/details/106539081?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522167748289416800213080657%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=167748289416800213080657&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-106539081-null-null.142^v73^pc_search_v2,201^v4^add_ask,239^v2^insert_chatgpt&utm_term=%E6%B3%A2%E7%89%B9%E7%8E%87&spm=1018.2226.3001.4187)
## 串口
串口,原名为串行接口(Serial Interface)或串列埠、序列埠,别名叫COM口(串行通讯端口cluster communi port)。PC机一般有两个串行口COM1和COM2。串行口与并行口区别在于它的**数据和控制信息是一位接一位地传送出去的。**传送速度慢,但**传送距离比并行口长**。**只要进行串行通讯的应该都属于串口。**

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "borrow"
version = "0.1.0"

@ -0,0 +1,8 @@
[package]
name = "borrow"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

@ -0,0 +1,10 @@
fn main() {
let mut s = String::from("hello");
{
let r1 = &s;
let r2 = &s;
println!("Hello, world!{},{}", r1, r2);
}
let r3 = &mut s;
println!("Hello, world!{}", r3);
}

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeRunConfigurationManager" shouldGenerate="true" shouldDeleteObsolete="true">
<generated />
</component>
<component name="CMakeSettings">
<configurations>
<configuration PROFILE_NAME="Debug" CONFIG_NAME="Debug" />
</configurations>
</component>
<component name="CargoProjects">
<cargoProject FILE="$PROJECT_DIR$/Cargo.toml" />
</component>
<component name="ChangeListManager">
<list default="true" id="1d19cf42-3d0a-4fe0-81e0-d06077e5150f" name="Default Changelist" comment="">
<change afterPath="$PROJECT_DIR$/../../../../.idea/inspectionProfiles/Project_Default.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/../../../../.idea/misc.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/../../../../.idea/modules.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/../../../../.idea/rust-zhu.iml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/../../../../.idea/vcs.xml" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="ClangdSettings">
<option name="formatViaClangd" value="false" />
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/../../../.." />
</component>
<component name="IgnoredFileRootStore">
<option name="generatedRoots">
<set>
<option value="C:\Users\10074\Desktop\rust-zhu\code\chapter\6\option\.idea" />
</set>
</option>
</component>
<component name="MacroExpansionManager">
<option name="directoryName" value="nfggakiu" />
</component>
<component name="ProjectId" id="2K2qqUDJ9npFogUqkXBFXw8RDV3" />
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showExcludedFiles" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="org.rust.cargo.project.model.PROJECT_DISCOVERY" value="true" />
</component>
<component name="RunManager">
<configuration name="Run option" type="CargoCommandRunConfiguration" factoryName="Cargo Command" temporary="true">
<option name="channel" value="DEFAULT" />
<option name="command" value="run --package option --bin option" />
<option name="allFeatures" value="false" />
<option name="nocapture" value="false" />
<option name="emulateTerminal" value="false" />
<option name="backtrace" value="SHORT" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<recent_temporary>
<list>
<item itemvalue="Cargo Command.Run option" />
</list>
</recent_temporary>
</component>
<component name="RustProjectSettings">
<option name="toolchainHomeDirectory" value="$USER_HOME$/.cargo/bin" />
<option name="version" value="2" />
</component>
<component name="SvnConfiguration">
<configuration />
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="1d19cf42-3d0a-4fe0-81e0-d06077e5150f" name="Default Changelist" comment="" />
<created>1673183215864</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1673183215864</updated>
<workItem from="1673183218594" duration="644000" />
<workItem from="1673191914562" duration="18000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="1" />
</component>
<component name="WindowStateProjectService">
<state x="471" y="236" key="#com.intellij.fileTypes.FileTypeChooser" timestamp="1673183228781">
<screen x="0" y="0" width="1536" height="816" />
</state>
<state x="471" y="236" key="#com.intellij.fileTypes.FileTypeChooser/0.0.1536.816@0.0.1536.816" timestamp="1673183228781" />
<state width="1493" height="295" key="GridCell.Tab.0.bottom" timestamp="1673191931773">
<screen x="0" y="0" width="1536" height="816" />
</state>
<state width="1493" height="295" key="GridCell.Tab.0.bottom/0.0.1536.816@0.0.1536.816" timestamp="1673191931773" />
<state width="1493" height="295" key="GridCell.Tab.0.center" timestamp="1673191931773">
<screen x="0" y="0" width="1536" height="816" />
</state>
<state width="1493" height="295" key="GridCell.Tab.0.center/0.0.1536.816@0.0.1536.816" timestamp="1673191931773" />
<state width="1493" height="295" key="GridCell.Tab.0.left" timestamp="1673191931773">
<screen x="0" y="0" width="1536" height="816" />
</state>
<state width="1493" height="295" key="GridCell.Tab.0.left/0.0.1536.816@0.0.1536.816" timestamp="1673191931773" />
<state width="1493" height="295" key="GridCell.Tab.0.right" timestamp="1673191931773">
<screen x="0" y="0" width="1536" height="816" />
</state>
<state width="1493" height="295" key="GridCell.Tab.0.right/0.0.1536.816@0.0.1536.816" timestamp="1673191931773" />
<state x="422" y="257" key="com.intellij.ide.util.TipDialog" timestamp="1673191914448">
<screen x="0" y="0" width="1536" height="816" />
</state>
<state x="422" y="257" key="com.intellij.ide.util.TipDialog/0.0.1536.816@0.0.1536.816" timestamp="1673191914448" />
</component>
</project>

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "dangling"
version = "0.1.0"

@ -0,0 +1,8 @@
[package]
name = "dangling"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

@ -0,0 +1,13 @@
fn main() {
let reference_to_nothing = dangle();
println!("res is {}", reference_to_nothing);
}
fn dangle() -> String { // dangle returns a reference to a String
let s = String::from("hello"); // s is a new String
s // we return a reference to the String, s
} // Here, s goes out of scope, and is dropped. Its memory goes away.
// Danger!

@ -0,0 +1,2 @@
# Default ignored files
/workspace.xml

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="CPP_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/fahrenheit_and_celsius.iml" filepath="$PROJECT_DIR$/.idea/fahrenheit_and_celsius.iml" />
</modules>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "fahrenheit_and_celsius"
version = "0.1.0"

@ -0,0 +1,8 @@
[package]
name = "fahrenheit_and_celsius"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

@ -0,0 +1,46 @@
use std::io;
fn main() {
loop {
println!("请输入您的体温: ");
let mut temp = String::new();
io::stdin()
.read_line(&mut temp)
.expect("输入数据有误");
let temp: f64 = temp.trim().parse().expect("类型转换失败");
println!("请输入温度单位(F或C):(若无特殊设置,默认大于50度为华氏度):");
let mut unit = String::new();
io::stdin()
.read_line(&mut unit)
.expect("输入数据有误");
let unit2: bool = match unit.trim() {
"F" => true,
"C" => false,
_ => {
if temp > 50.0 {
println!("使用华氏温度单位(F)");
true
} else {
println!("使用摄氏温度单位(C)");
false
}
}
};
println!("体温 is: {}", temp);
println!("类型 is: {}", unit2);
let res = trans(temp, unit2);
println!("你的体温 is: {}", res);
}
}
fn trans(temp:f64, is_fahrenheit:bool)->f64{
if is_fahrenheit{
(temp-32.0)*5.0/9.0
}else {
temp*9.0/5.0 +32.0
}
}

@ -0,0 +1 @@
/target

@ -0,0 +1,2 @@
# Default ignored files
/workspace.xml

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="CPP_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/fib.iml" filepath="$PROJECT_DIR$/.idea/fib.iml" />
</modules>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "fib"
version = "0.1.0"

@ -0,0 +1,8 @@
[package]
name = "fib"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

@ -0,0 +1,21 @@
use std::io;
fn main() {
println!("请输入你要查的队列下标");
let mut n = String::new();
io::stdin()
.read_line(&mut n)
.expect("数据输入有误");
let n:i32 = n.trim().parse().expect("类型转换失败");
let mut a = 0;
let mut b = 1;
for i in 0..n{
let c = a+b;
a = b;
b = c;
}
println!("res is: {}", a);
}

@ -0,0 +1,75 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "getrandom"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "guessing_game"
version = "0.1.0"
dependencies = [
"rand",
]
[[package]]
name = "libc"
version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"

@ -0,0 +1,9 @@
[package]
name = "guessing_game"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rand = "0.8.5"

@ -0,0 +1,35 @@
use std::io;
use rand::Rng;
use std::cmp::Ordering;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The Secret number is : {secret_number}");
loop{
let mut guess = String::new();
println!("Please input your guess.");
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = match guess.trim().parse(){
Ok(num)=>num,
Err(_)=>continue,
};
println!("You guessed: {guess}");
match guess.cmp(&secret_number){
Ordering::Less => println!("Too small!"),
Ordering::Greater=> println!("Too big!"),
Ordering::Equal=>{
println!("you win!");
break;
}
}
}
}

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "new_first_word"
version = "0.1.0"

@ -0,0 +1,8 @@
[package]
name = "new_first_word"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

@ -0,0 +1,34 @@
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for(i, &item) in bytes.iter().enumerate(){
if item == b' '{
return &s[0..i];
}
}
&s[..]
}
fn main(){
let s = String::from("hello world");
let word = first_word(&s[0..6]);
println!("res is1: {}", word);
let word = first_word(&s[..]);
println!("res is2: {}", word);
let word = first_word(&s);
println!("res is3: {}", word);
let s_literal = "hello world";
let word = first_word(&s_literal[0..6]);
println!("res is11: {}", word);
let word = first_word(&s_literal[0..6]);
println!("res is12: {}", word);
let word = first_word(s_literal);
println!("res is13: {}", word);
println!("res is: {}", s);
}

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "other_slice"
version = "0.1.0"

@ -0,0 +1,8 @@
[package]
name = "other_slice"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

@ -0,0 +1,5 @@
fn main() {
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
println!("{:?}", slice);
}

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "overflow"
version = "0.1.0"

@ -0,0 +1,8 @@
[package]
name = "overflow"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

@ -0,0 +1,5 @@
fn main() {
let a:u8 = 256;
println!("Hello, world!{a}");
}

@ -0,0 +1,2 @@
# Default ignored files
/workspace.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/ownership.iml" filepath="$PROJECT_DIR$/.idea/ownership.iml" />
</modules>
</component>
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="CPP_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save