parent
98c122026f
commit
b46574738d
@ -1,2 +1,2 @@ |
||||
# 屏蔽 Rust 项目中的 target 目录 |
||||
/target/ |
||||
**/target/ |
||||
|
@ -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,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,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,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,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,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,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,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] |
Binary file not shown.
@ -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 @@ |
||||
/target |
@ -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] |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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 @@ |
||||
/target |
@ -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" |
Binary file not shown.
@ -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 @@ |
||||
/target |
@ -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 @@ |
||||
/target |
@ -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…
Reference in new issue