This commit is contained in:
Davirain
2023-05-18 10:38:12 +08:00
parent fecb86d621
commit e10f8d7b1c
37 changed files with 1602 additions and 1313 deletions

View File

@@ -32,7 +32,15 @@
- [BTreeMap](./chapter_3/chapter_3_15_6.md)
- [BTreeSet](./chapter_3/chapter_3_15_7.md)
- [智能指针](./chapter_3/chapter_3_16.md)
- [包、crate、模块-todo](./chapter_3/chapter_3_17.md)
- [智能指针介绍](./chapter_3/chapter_3_16_1.md)
- [Box智能指针](./chapter_3/chapter_3_16_2.md)
- [Deref trait](./chapter_3/chapter_3_16_3.md)
- [Drop trait](./chapter_3/chapter_3_16_4.md)
- [Rc智能指针](./chapter_3/chapter_3_16_5.md)
- [RefCell智能指针](./chapter_3/chapter_3_16_6.md)
- [引用循环、内存泄露、Weak智能指针](./chapter_3/chapter_3_16_7.md)
- [包、crate、模块](./chapter_3/chapter_3_17.md)
- [包、crate和模块介绍](./chapter_3/chapter_3_17_1.md)
- [测试-todo](./chapter_3/chapter_3_18.md)
- [再谈注释-todo](./chapter_3/chapter_3_19.md)
- [Rust并发编程](./chapter_3/chapter_3_20.md)

View File

@@ -1,7 +1,9 @@
# 1 前言:为什么写这本书
和Rust结缘是2019年当时为了能看懂libra的源码开始了Rust的学习。起初的目标只是能看懂Rust的代码就好谁知一看竟然发现这门编程语言很对我的胃口于是开启了真正的Rust学习之路。彼时的工作中并没有使用Rust但是周围的小伙伴有在研究谷歌操作系统fuchsia跟他们的交流更加坚定了我要学习这门编程语言的想法。
所谓“拳不离手曲不离口”要能熟练的掌握一门编程语言必须要有大量的练习但是因为在工作中并不会使用Rust这无疑会让Rust学习的效果打折扣。为了促使自己不断的学习输入我想到了边学习边录制视频然后将视频放到B站上。虽然当时对Rust的理解不够深入甚至是有些视频中还有错误但还是让我有了一批一起学习Rust的粉丝。
2021年换工作后算是真正开始使用Rust了在真正使用Rust的过程中也曾产生过撰写一本Rust教程的想法。但是一直感觉写书是一件严肃的事情更何况自己的Rust水平也只能算是个熟练的使用者而已。直到最近和DaviRain聊天说起如果能有办法降低一点点Rust入门的难度也算是给Rust做了一点贡献于是决定和DaviRain写这本书。
所以本书定位就是为了方便入门Rust在本书中
@@ -12,7 +14,7 @@
本书撰写过程中我们参考了其它的Rust书籍参考的书籍主要有《The Rust Programming Language》、《Rust语言圣经》、《Rust入门秘籍》、《Rust第一课》等。本书第2.1节为什么选择Rust是由chatGPT4进行生成第3.8节复合数据类型、第3.15节常见的Collections、第4章Rust使用技巧由DaviRain完成第3.9节Trait部分由DaviRain和令狐壹冲一起完成其它章节由令狐壹冲完成。但是由于我们自身水平的问题本书必然存在一些错误和描述不清楚的地方希望各位读者能够不吝指正
愿本书能让您的Rust入门之路能够变得不再崎岖
令狐壹冲
令狐壹冲

View File

@@ -1,13 +1,16 @@
# 2 欢迎来到Rust的世界
## 2.1 为什么选择Rust
### 2.1.1 Rust的历史与背景
Rust是一种现代的系统编程语言它注重性能、安全性和并发。Rust最初由Mozilla研究院的Graydon Hoare于2006年开始设计最早的目标是为了解决C++在系统编程领域的一些痛点。在2010年Mozilla正式开始支持这个项目从那时起Rust开始迅速发展并逐渐成为一个强大的编程语言。
1. 设计理念
#### 1. 设计理念
Rust的设计理念是将系统编程的性能与安全性相结合。它的核心创新是引入了一套所有权系统这套系统能在编译时检测许多常见的内存错误如空指针解引用、数据竞争等。这种设计使得Rust在保持C和C++级别性能的同时,提供了更高的内存安全性。
2. 发展历程
#### 2. 发展历程
- 2006年Graydon Hoare开始设计Rust。
- 2009年Mozilla开始关注Rust希望它能成为一种更安全的系统编程语言。
@@ -18,36 +21,37 @@ Rust的设计理念是将系统编程的性能与安全性相结合。它的核
- 2016年Rust开始获得广泛关注社区逐渐壮大。
- 2018年WebAssembly的推广使得Rust成为一个受欢迎的前端编程语言。
3. 社区与生态系统
#### 3. 社区与生态系统
随着Rust的发展其社区和生态系统也在不断壮大。Rust编程语言被广泛应用于各种领域如网络编程、游戏开发、操作系统、嵌入式系统、区块链等。此外Rust已经连续多年被Stack Overflow开发者调查评为最受欢迎的编程语言。
Rust的成功和流行归功于其活跃的社区和丰富的第三方库。社区不断努力改进和扩展Rust的功能使其成为一个更加实用和强大的编程语言。总的来说Rust的历史与背景显示了一个充满创新、活力和潜力的编程语言它将继续为现代软件开发带来更多的机会。
### 2.1.2 Rust的主要优势
Rust的设计目标是为程序员提供一种高性能、安全且具有现代化特性的系统编程语言。以下是Rust的一些主要优势
1. 内存安全性
#### 1. 内存安全性
Rust通过其独特的所有权系统、生命周期和借用检查器确保了内存安全。这些功能让Rust能够在编译时检测许多常见的内存错误如悬垂指针、空指针解引用、数据竞争等。这种设计减少了内存泄漏、悬垂指针等问题的出现从而使得编写安全的代码变得更加容易。
2. 高性能
#### 2. 高性能
Rust注重零开销抽象zero-cost abstractions这意味着Rust提供的高级抽象不会对程序性能产生负面影响。Rust的性能与C和C++相当,这使得它成为一个理想的选择,尤其是对于对性能要求较高的系统编程任务。
3. 并发友好
#### 3. 并发友好
Rust的内存模型和类型系统让并发变得更加简单和安全。通过提供原子操作和线程安全的数据结构Rust在编译时就可以预防数据竞争等多线程问题。这使得Rust在多核处理器和分布式系统领域具有优势。
4. 易于集成
#### 4. 易于集成
Rust的C兼容的FFIForeign Function Interface允许轻松地与其他编程语言集成。这使得Rust可以逐步替换现有的C和C++代码,提高系统的安全性和性能,而无需重写整个项目。
5. 生产力与现代化特性
#### 5. 生产力与现代化特性
Rust提供了许多现代编程语言的特性如模式匹配、类型推断、闭包等。这些特性可以提高程序员的生产力使得编写代码更加愉快。Rust的丰富的标准库和第三方库也使得开发者能够轻松地找到所需的功能。
6. 活跃的社区与生态系统
#### 6. 活跃的社区与生态系统
Rust拥有一个友好、活跃且不断壮大的社区。开发者们分享知识、讨论问题、改进编译器和标准库这使得Rust不断进化和完善。Rust生态系统的成熟也使得开发者能够更容易地找到和使用高质量的第三方库。
@@ -67,51 +71,53 @@ Rust拥有一个友好、活跃且不断壮大的社区。开发者们分享知
Rust的高性能、内存安全和现代化特性使其在许多领域具有广泛的应用。以下是一些Rust在不同领域中的典型应用
1. 网络编程
#### 1. 网络编程
在网络编程领域Rust的性能和安全性特性使其成为构建可靠、高性能服务器和网络应用的理想选择。例如Tokio是一个使用Rust编写的高性能异步运行时它可以让开发者轻松地构建高吞吐量、低延迟的服务。同时Hyper是一个快速的HTTP库可以用于构建网络客户端和服务器。
2. WebAssembly
#### 2. WebAssembly
Rust在WebAssembly开发中具有很大的优势。由于其性能和安全性Rust成为了一个受欢迎的WebAssembly目标语言。通过将Rust编译为WebAssembly开发者可以构建高性能、安全的前端应用程序提高网页的加载速度和运行效率。
3. 游戏开发
#### 3. 游戏开发
Rust在游戏开发领域具有很大的潜力。其高性能和内存安全性特性使得Rust成为游戏开发的理想选择。Amethyst和Bevy是两个使用Rust编写的游戏引擎它们为开发者提供了丰富的功能和性能优势。同时Rust也可以与现有的游戏引擎如Unity和Unreal Engine进行集成提供更安全的原生插件和后端服务。
4. 嵌入式系统和物联网
#### 4. 嵌入式系统和物联网
Rust在嵌入式系统和物联网领域具有优势。由于其高性能、低内存占用和安全性特性Rust成为了嵌入式设备开发的理想选择。使用Rust可以更容易地构建可靠、安全的嵌入式系统。此外Rust在实时操作系统RTOS如Tock OS等项目中也得到了应用。
5. 操作系统开发
#### 5. 操作系统开发
Rust在操作系统开发中具有很大的潜力。其内存安全和高性能特性使得Rust成为开发创新操作系统的理想选择。例如Redox OS是一个使用Rust编写的现代化、安全的微内核操作系统。
6. 区块链技术
#### 6. 区块链技术
在区块链领域Rust的高性能和安全性特性使其成为构建区块链系统的理想选择。一些知名的区块链项目如Parity和Solana都采用了Rust来实现其核心组件。这些项目展示了Rust在处理高并发、安全性要求高的场景中的能力。
7. 机器学习和数据科学
#### 7. 机器学习和数据科学
虽然Python在机器学习和数据科学领域占据主导地位但Rust也在逐渐成为一个有吸引力的选择。Rust的高性能特性使其在计算密集型任务中具有优势。ArrayFire-rust和ndarray等库提供了高性能的数组计算和线性代数功能而tch-rs等库则提供了Rust的Torch绑定允许在Rust中使用深度学习功能。
8. 跨平台开发
#### 8. 跨平台开发
Rust具有良好的跨平台支持可以轻松地在不同的操作系统和硬件架构上运行。这使得Rust成为构建跨平台应用的理想选择。例如使用Rust编写的GUI库如druid和iced可以帮助开发者轻松地构建跨平台的桌面应用。
9. 系统工具与实用程序
#### 9. 系统工具与实用程序
Rust在系统工具和实用程序开发中也表现出色。Rust编写的工具可以在性能、安全性和可维护性方面取得优异的表现。例如ripgrep是一个使用Rust编写的高性能grep工具它在速度和功能上超越了许多现有的grep实现。
10. Web开发
#### 10. Web开发
虽然Rust主要用于系统编程但它也在Web开发领域取得了一定的成功。使用Rocket、Actix Web和Tide等Web框架开发者可以用Rust构建高性能、安全的Web应用程序和API服务。
综上所述Rust在不同领域的应用表明了它作为一种通用编程语言的潜力。随着Rust社区和生态系统的不断壮大Rust将继续在各个领域发挥越来越重要的作用。
## 2.2 安装和配置Rust
### 2.2.1 安装Rust
1. 对于macOS和Linux用户
#### 1. 对于macOS和Linux用户
通过如下命令进行安装:
@@ -119,11 +125,11 @@ Rust在系统工具和实用程序开发中也表现出色。Rust编写的工具
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
```
2. 对于Windows用户
#### 2. 对于Windows用户
- 访问https://rustup.rs/ ,点击"Get started"。
- 下载rustup-init.exe安装程序。
- 双击rustup-init.exe运行安装程序按照提示操作。
- 访问[https://rustup.rs/](https://rustup.rs/) ,点击"Get started"。
- 下载`rustup-init.exe`安装程序。
- 双击`rustup-init.exe`运行安装程序,按照提示操作。
- 在安装过程中,选择默认选项(按回车键)即可。
- 安装完成后,重启计算机。
@@ -138,9 +144,11 @@ rustc --version
```
## 2.3 第一个Rust程序
本节将编写和运行本书的第一个Rust程序Hello, World!
本节将编写和运行本书的第一个Rust程序`Hello, World!`
### 2.3.1 创建一个新的Rust项目
Rust使用Cargo作为官方的构建工具和包管理器下面通过Cargo创建一个新的Rust项目
```bash
@@ -154,33 +162,38 @@ cargo new hello_world
其中Cargo.toml是项目的配置文件src为源代码目录main.rs为主程序文件。
### 2.3.2 编写程序
默认情况下main.rs文件中已经包含了一个简单的Hello, World程序代码如下
默认情况下main.rs文件中已经包含了一个简单的`Hello, World`程序,代码如下:
```Rust
fn main() {
println!("Hello, world!");
}
```
其中main函数是Rust程序的入口而println!则是一个Rust宏而非函数用于在标准输出上打印一行文本。
其中main函数是Rust程序的入口`println!`则是一个Rust宏而非函数用于在标准输出上打印一行文本。
### 2.3.3 编译并运行Hello, World程序
运行程序需要进去到项目目录中然后运行cargo run命令即可步骤如下
运行程序需要进去到项目目录中,然后运行`cargo run`命令即可,步骤如下:
```bash
cd hello_world
cargo run
```
运行结果如下:
![运行结果](../assets/63.png)
### 2.3.4 理解Rust源码的基本结构
下面简单介绍Rust源码的结构。
1. main函数
#### 1. main函数
main函数是程序的入口。当运行程序时main函数将首先被调用。Rust中函数的定义以关键字fn开始后跟函数名和参数列表函数体则由一对花括号包围。main函数没有参数也没有返回值。
2. println!宏
#### 2. println!宏
println!是一个用于向控制台输出一行文本的Rust宏。在例子中我们向println!宏传递了一个字符串字面量"Hello, World!"作为参数。println!宏会将这个字符串作为控制台输出。
`println!`是一个用于向控制台输出一行文本的Rust宏。在例子中我们向`println!`宏传递了一个字符串字面量`"Hello, World!"`作为参数。`println!`宏会将这个字符串作为控制台输出。

View File

@@ -38,8 +38,8 @@ Rust中变量分为不可变变量和可变变量。不可变变量不能对其
b = 3;
```
设计思考:
从编译器的角度如果一个值定义为不可变变量那它就不会改变更易于推导。想想一下如果代码非常多如果变量不会变化但是允许它可变其实会更容易滋生bug。
> 设计思考:
> 从编译器的角度如果一个值定义为不可变变量那它就不会改变更易于推导。想想一下如果代码非常多如果变量不会变化但是允许它可变其实会更容易滋生bug。
## 3.1.3常量

View File

@@ -19,267 +19,270 @@ pub trait GetInformation {
## 2. 为类型实现trait
- 为类型实现trait
### 2.1 为类型实现trait
代码示例如下:
```rust
// 定义trait
pub trait GetInformation {
fn get_name(&self) -> &String;
fn get_age(&self) -> u32;
}
代码示例如下:
```rust
// 定义trait
pub trait GetInformation {
fn get_name(&self) -> &String;
fn get_age(&self) -> u32;
}
pub struct Student {
pub name: String,
pub age: u32,
pub struct Student {
pub name: String,
pub age: u32,
}
// 为Student类型实现GetInformation trait
impl GetInformation for Student {
fn get_name(&self) -> &String {
&self.name
}
// 为Student类型实现GetInformation trait
impl GetInformation for Student {
fn get_name(&self) -> &String {
&self.name
}
fn get_age(&self) -> u32 {
self.age
}
fn get_age(&self) -> u32 {
self.age
}
}
pub struct Teacher {
pub name: String,
pub age: u32,
pub struct Teacher {
pub name: String,
pub age: u32,
}
// 为Teacher类型实现GetInformation trait
impl GetInformation for Teacher {
fn get_name(&self) -> &String {
&self.name
}
// 为Teacher类型实现GetInformation trait
impl GetInformation for Teacher {
fn get_name(&self) -> &String {
&self.name
}
fn get_age(&self) -> u32 {
self.age
}
fn get_age(&self) -> u32 {
self.age
}
}
fn main() {
let s = Student {
name: "alice".to_string(),
age: 18,
};
// 可以在类型Student上使用GetInfomation trait中定义的方法
println!("s.name = {:?}, s.age = {:?}", s.get_name(), s.get_age());
fn main() {
let s = Student {
name: "alice".to_string(),
age: 18,
};
// 可以在类型Student上使用GetInfomation trait中定义的方法
println!("s.name = {:?}, s.age = {:?}", s.get_name(), s.get_age());
let t = Teacher {
name: "bob".to_string(),
age: 25,
};
// 可以类型Teacher使用GetInfomation trait中定义的方法
println!("t.name = {:?}, t.age = {:?}", t.get_name(), t.get_age());
}
```
let t = Teacher {
name: "bob".to_string(),
age: 25,
};
// 可以类型Teacher使用GetInfomation trait中定义的方法
println!("t.name = {:?}, t.age = {:?}", t.get_name(), t.get_age());
}
```
- 可以在trait定义时提供默认实现
### 2.2 可以在trait定义时提供默认实现
可以在定义trait的时候提供默认的行为trait的类型可以使用默认的行为示例如下
```rust
// 定义trait
pub trait GetInformation {
fn get_name(&self) -> &String;
fn get_age(&self) -> u32 { // 在定义trait时就提供默认实现
25u32
}
可以在定义trait的时候提供默认的行为trait的类型可以使用默认的行为示例如下
```rust
// 定义trait
pub trait GetInformation {
fn get_name(&self) -> &String;
fn get_age(&self) -> u32 { // 在定义trait时就提供默认实现
25u32
}
}
pub struct Student {
pub name: String,
pub age: u32,
pub struct Student {
pub name: String,
pub age: u32,
}
// 为Student类型实现GetInformation trait
impl GetInformation for Student {
fn get_name(&self) -> &String {
&self.name
}
// 为Student类型实现GetInformation trait
impl GetInformation for Student {
fn get_name(&self) -> &String {
&self.name
}
// 实现get_age方法Student不会使用trait定义时的默认实现
fn get_age(&self) -> u32 {
self.age
}
// 实现get_age方法Student不会使用trait定义时的默认实现
fn get_age(&self) -> u32 {
self.age
}
}
pub struct Teacher {
pub name: String,
pub age: u32,
}
// 为Teacher类型实现GetInformation trait
impl GetInformation for Teacher {
fn get_name(&self) -> &String {
&self.name
}
// 不实现get_age方法将使用trait的默认实现
pub struct Teacher {
pub name: String,
pub age: u32,
}
// 为Teacher类型实现GetInformation trait
impl GetInformation for Teacher {
fn get_name(&self) -> &String {
&self.name
}
// 不实现get_age方法将使用trait的默认实现
}
fn main() {
let s = Student {
name: "alice".to_string(),
age: 18,
};
// 可以在类型Student上使用GetInfomation trait中定义的方法
// 输出t.name = "bob", t.age = 25
println!("s.name = {:?}, s.age = {:?}", s.get_name(), s.get_age());
fn main() {
let s = Student {
name: "alice".to_string(),
age: 18,
};
// 可以在类型Student上使用GetInfomation trait中定义的方法
// 输出t.name = "bob", t.age = 25
println!("s.name = {:?}, s.age = {:?}", s.get_name(), s.get_age());
let t = Teacher {
name: "bob".to_string(),
age: 25,
};
// 可以类型Teacher使用GetInfomation trait中定义的方法t.get_age将使用定义trait时的默认方法
// 输出t.name = "bob", t.age = 25
println!("t.name = {:?}, t.age = {:?}", t.get_name(), t.get_age());
}
```
let t = Teacher {
name: "bob".to_string(),
age: 25,
};
// 可以类型Teacher使用GetInfomation trait中定义的方法t.get_age将使用定义trait时的默认方法
// 输出t.name = "bob", t.age = 25
println!("t.name = {:?}, t.age = {:?}", t.get_name(), t.get_age());
}
```
如果定义trait时提供了某个方法的默认实现
- 如果为类型实现该trait时为该类型实现了此方法则使用自己实现的方法如上面示例中的Student类型
- 如果为类型实现该trait时没有为该类型实现此方法则该类型使用trait提供的默认实现如上面的Teacher类型
- 如果为类型实现该trait时为该类型实现了此方法则使用自己实现的方法如上面示例中的Student类型
- 如果为类型实现该trait时没有为该类型实现此方法则该类型使用trait提供的默认实现如上面的Teacher类型
## 3. trait作为参数
- trait作为参数
trait可以用来参数示例如下
### 3.1 trait作为参数
```rust
// 定义trait
pub trait GetInformation {
fn get_name(&self) -> &String;
fn get_age(&self) -> u32 {
25u32
}
trait可以用来参数示例如下
```rust
// 定义trait
pub trait GetInformation {
fn get_name(&self) -> &String;
fn get_age(&self) -> u32 {
25u32
}
}
pub struct Student {
pub name: String,
pub age: u32,
pub struct Student {
pub name: String,
pub age: u32,
}
// 为Student类型实现GetInformation trait
impl GetInformation for Student {
fn get_name(&self) -> &String {
&self.name
}
// 为Student类型实现GetInformation trait
impl GetInformation for Student {
fn get_name(&self) -> &String {
&self.name
}
fn get_age(&self) -> u32 {
self.age
}
fn get_age(&self) -> u32 {
self.age
}
}
pub struct Teacher {
pub name: String,
pub age: u32,
pub struct Teacher {
pub name: String,
pub age: u32,
}
// 为Teacher类型实现GetInformation trait
impl GetInformation for Teacher {
fn get_name(&self) -> &String {
&self.name
}
// 为Teacher类型实现GetInformation trait
impl GetInformation for Teacher {
fn get_name(&self) -> &String {
&self.name
}
}
// 参数类型必须是实现了GetInfomation trait的类型
pub fn print_information(item: impl GetInformation) {
println!("name = {}", item.get_name());
println!("age = {}", item.get_age());
}
fn main() {
let s = Student {
name: "alice".to_string(),
age: 18,
};
print_information(s);
let t = Teacher {
name: "bob".to_string(),
age: 25,
};
print_information(t);
}
```
在上面的例子中,函数`pub fn print_information(item: impl GetInformation)`的要求参数item必须实现`GetInformation trait`,否则无法调用该参数。
### 3.2 使用`trait bound`语法
上面中的print_information函数还可以写成如下
```rust
// 使用trait bound的写法一
pub fn print_information<T: GetInformation>(item: T) {
println!("name = {}", item.get_name());
println!("age = {}", item.get_age());
}
```
这种写法叫做Trait bound语法它是Rust中用于指定泛型类型参数所需的trait的一种方式它还可以使用where关键字写成如下
```rust
// 使用trait bound的写法二
pub fn print_information<T>(item: T)
where
T: GetInformation,
{
println!("name = {}", item.get_name());
println!("age = {}", item.get_age());
}
```
### 3.3 通过“`+`”指定多个trait bound
可以要求类型实现多个trait示例如下
```rust
pub trait GetName {
fn get_name(&self) -> &String;
}
pub trait GetAge {
fn get_age(&self) -> u32;
}
//使用trait bound写法一类型T必须实现GetName和GetAge trait
pub fn print_information1<T: GetName + GetAge>(item: T) {
println!("name = {}", item.get_name());
println!("age = {}", item.get_age());
}
//使用trait bound写法二类型T必须实现GetName和GetAge trait
pub fn print_information2<T>(item: T)
where
T: GetName + GetAge,
{
println!("name = {}", item.get_name());
println!("age = {}", item.get_age());
}
#[derive(Clone)]
struct Student {
name: String,
age: u32,
}
impl GetName for Student {
fn get_name(&self) -> &String {
&self.name
}
}
// 参数类型必须是实现了GetInfomation trait的类型
pub fn print_information(item: impl GetInformation) {
println!("name = {}", item.get_name());
println!("age = {}", item.get_age());
impl GetAge for Student {
fn get_age(&self) -> u32 {
self.age
}
}
fn main() {
let s = Student {
name: "alice".to_string(),
age: 18,
};
print_information(s);
fn main() {
let s = Student {
name: "alice".to_string(),
age: 18u32,
};
print_information1(s.clone());
print_information1(s);
}
```
let t = Teacher {
name: "bob".to_string(),
age: 25,
};
print_information(t);
}
```
在上面的例子中,函数`pub fn print_information(item: impl GetInformation)`的要求参数item必须实现`GetInformation trait`,否则无法调用该参数。
- 使用`trait bound`语法
上面中的print_information函数还可以写成如下
```rust
// 使用trait bound的写法一
pub fn print_information<T: GetInformation>(item: T) {
println!("name = {}", item.get_name());
println!("age = {}", item.get_age());
}
```
这种写法叫做Trait bound语法它是Rust中用于指定泛型类型参数所需的trait的一种方式它还可以使用where关键字写成如下
```rust
// 使用trait bound的写法二
pub fn print_information<T>(item: T)
where
T: GetInformation,
{
println!("name = {}", item.get_name());
println!("age = {}", item.get_age());
}
```
- 通过“`+`”指定多个trait bound
可以要求类型实现多个trait示例如下
```rust
pub trait GetName {
fn get_name(&self) -> &String;
}
pub trait GetAge {
fn get_age(&self) -> u32;
}
//使用trait bound写法一类型T必须实现GetName和GetAge trait
pub fn print_information1<T: GetName + GetAge>(item: T) {
println!("name = {}", item.get_name());
println!("age = {}", item.get_age());
}
//使用trait bound写法二类型T必须实现GetName和GetAge trait
pub fn print_information2<T>(item: T)
where
T: GetName + GetAge,
{
println!("name = {}", item.get_name());
println!("age = {}", item.get_age());
}
#[derive(Clone)]
struct Student {
name: String,
age: u32,
}
impl GetName for Student {
fn get_name(&self) -> &String {
&self.name
}
}
impl GetAge for Student {
fn get_age(&self) -> u32 {
self.age
}
}
fn main() {
let s = Student {
name: "alice".to_string(),
age: 18u32,
};
print_information1(s.clone());
print_information1(s);
}
```
在上面的代码中,`print_information1`和`print_information2`函数要求其参数类型T必须实现`GetName`和`GetAge`两个trait通过`+`来进行多个约束的连接。
在上面的代码中,`print_information1``print_information2`函数要求其参数类型T必须实现`GetName``GetAge`两个trait通过`+`来进行多个约束的连接。
## 4. 返回trait的类型
trait类型可以作为函数的返回类型示例如下
```rust
pub trait GetName {
fn get_name(&self) -> &String;
@@ -310,6 +313,7 @@ fn main() {
```
上面代码中,`produce_item_with_name`函数返回了一个实现了`GetName trait`的类型。不过需要注意的是,这种方式返回的是单一类型,例如如下的代码就是错误的,无法编译通过:
```rust
pub trait GetName {
fn get_name(&self) -> &String;
@@ -357,6 +361,7 @@ fn main() {
错误原因分析(非常重要):
上面的代码中的`produce_item_with_name`函数的定义实际上等价于如下:
```rust
pub fn produce_item_with_name<T: GetName>(is_teacher: bool) -> T {
...
@@ -506,4 +511,5 @@ fn main() {
s.print_name(); //student实现了GetName trait因此可是直接使用PrintName trait中的函数print_name
}
```
上面的例子中,就是为实现了`GetName trait`的类型实现`PrintName trait`

View File

@@ -1,10 +1,10 @@
# 3.10.2 trait对象
在上一节中第4点返回trait对象时提到了produce_item_with_name函数返回的是单一的类型即在编译时就确定了具体的类型因此produce_item_with_name无法正确编译。为解决这种问题Rust中引入了trait对象。
在上一节中第4点返回trait对象时提到了`produce_item_with_name`函数返回的是单一的类型,即在编译时就确定了具体的类型,因此`produce_item_with_name`无法正确编译。为解决这种问题Rust中引入了trait对象。
## 1. 使用trait对象
在Rust中trait自身不能当作数据类型来使用但trait 对象可以当作数据类型使用。因此可以将实现了Trait A的类型B、C、D当作trait A的trait对象来使用。使用trait对象时基本都是以引用的方式使用所以使用时通常是引用符号加dyn关键字即&dyn
在Rust中trait自身不能当作数据类型来使用但trait 对象可以当作数据类型使用。因此,可以将实现了`Trait A`的类型`B``C``D`当作`trait A`的trait对象来使用。使用trait对象时基本都是以引用的方式使用所以使用时通常是引用符号加`dyn`关键字(即`&dyn`)。
示例如下:
```Rust
@@ -61,20 +61,22 @@ fn main() {
chalie.get_name();
}
```
上面代码中Student和Teacher都实现了GetName trait因此这两种类型可以当做GetName的trait对象使用。
上面代码中,`Student``Teacher`都实现了`GetName trait`,因此这两种类型可以当做`GetName`的trait对象使用。
## 2. trait对象动态分发的原理
对于trait对象需要说明如下几点
- trait 对象大小不固定这是因为对于trait A类型B可以实现trait A类型C也可以实现trait A因此A trait对象的大小是无法确定的因为可能是B类型也可能是C类型
- trait 对象大小不固定:这是因为,对于`trait A`,类型`B`可以实现`trait A`,类型`C`也可以实现`trait A`因此A trait对象的大小是无法确定的因为可能是B类型也可能是C类型
- 使用trait对象时总是使用它们的引用的方式
- 虽然trait对象没有固定大小但它的引用类型的大小固定它由两个指针组成因此占两个指针大小。
- 一个指针指向具体类型的实例。
- 另一个指针指向一个虚表vtablevtable中保存了实例对于可以调用的实现于trait的方法。当调用方法时直接从vtable中找到方法并调用。
- trait对象的引用方式有多种。例如对于trait A其trait对象类型的引用可以是&dyn ABox<dyn A>Rc<dyn A>等。
- trait对象的引用方式有多种。例如对于`trait A`其trait对象类型的引用可以是`&dyn A``Box<dyn A>``Rc<dyn A>`等。
下面通过一段代码来分析一下使用trait对象时内存的布局。代码如下
```Rust
trait Vehicle {
fn run(&self);
@@ -110,28 +112,31 @@ fn main() {
vehicle1.run();
vehicle2.run();
}
```
vehicle1和vehicle1都是Vehicle trait对象的引用vehicle1来说trait对象的具体类型是Carvehicle2来说trait对象的具体类型是Truck
```
在上面的代码中vehicle1和vehicle1都是Vehicle trait对象的引用对于vehicle1来说trait对象的具体类型是`Car`对于vehicle2来说trait对象的具体类型是`Truck`。上面代码对应的内存布局如下:
![注释](../../assets/14.png)
变量car和变量truck分别是Car类型和Truck类型存储在栈上vehicle1和vehicle2是Vehicle trait对象的引用具有两个指针其中指针ptr指向具体类型的实例vptr指向虚函数表虚函数表存储在只读数据区。
.rodatatrait对象的时候vehicle1.run()vehicle2.run()
更进一步的理解,虚函数表存储在程序的可执行文件中的只读数据段(.rodata这个只读数据段在程序运行时被加载到内存中因此虚函数表也是只读的。实现trait对象的时候编译器会在对象的内存布局中添加一个指向虚函数表的指针这个指针被称为虚函数表指针。在程序运行到`vehicle1.run()``vehicle2.run()`时,程序通过虚函数表找到对应的函数指针,然后来执行。
## 3. trait对象要求对象安全
只有对象安全object safe的 trait 才可以组成 trait 对象。trait的方法满足以下两条要求才是对象安全的
- Self
- 返回值类型不为 `Self`
- 方法没有任何泛型类型参数。
***Selftrait对象在产生时Self具体是什么类型不知道
Rust用带泛型的类型在编译时会做单态化trait对象是运行时才确定西西***
> 分析:
>
>***不允许返回`Self`是因为trait对象在产生时原来的具体的类型会被抹去Self具体是什么类型不知道所以编译会报错
> 不允许携带泛型参数是因为Rust用带泛型的类型在编译时会做单态化而trait对象是运行时才确定即一个运行时才能确定的东西里又包含一个需要在编译时确定的东西相互冲突必然是不行的***。
如下代码编译会报错,因为`Clone`返回的是`Self`
Clone返回的是Self
```Rust
pub struct Screen {
pub components: Vec<Box<dyn Clone>>,
}
}
```

View File

@@ -93,6 +93,7 @@ impl Add for Point {
```
## 7. std::iter::Iterator
```rust
struct Counter {
count: u32,

View File

@@ -1,26 +1,32 @@
# 3.11 生命周期
Rust中的生命周期是用来管理内存的一种机制。在Rust中内存的所有权和使用必须是确定的生命周期就是用来确定内存的使用范围的说的更具体点就是确定引用的有效范围:
- 编译器大多数时间能够自动推导生命周期3.11.1和3.11.2中的例子编译器都能自动推导);
- 在多种类型存在,且编译器无法推导某个引用的生命周期时,需要在代码中显式的标明生命周期。
## 3.11.1 悬垂指针和生命周期
生命周期的主要目的就是为了避免悬垂引用。考虑如下代码:
```Rust
fn main() {
let r;
{
let x = 5;
let x = 5;
r = &x;
}
println!("r = {}", r); //r为悬垂引用
}
```
上面代码中x的有效作用域是从第4行到第6行花括号结束前即从第6行的花括号后开始x变为无效r为x的应用此时将指向的是一块无效的内存。其内存示意图如下
![注释](../../assets/15.png)
## 3.11.2 借用检查
Rust编译器有一个借用检查器用它来检查所有的应用的都是有效的具体的方式为比较变量及其引用的作用域。
### 1. 示例1如下
```Rust
fn main() {
@@ -33,9 +39,11 @@ fn main() {
println!("r = {}", r); // | //r为悬垂引用
} //---------------------------------------+
```
对于上面的代码,借用检查器将r的生命周期标注为'a将x的生命周期标注为'b然后比较'a和'b的范围,发现'b < 'a被引用的对象比它的引用者存在的时间还短然后编译报错。
对于上面的代码,借用检查器将`r`的生命周期标注为`'a`将x的生命周期标注为`'b`,然后比较`'a``'b`的范围,发现`'b < 'a`,被引用的对象比它的引用者存在的时间还短,然后编译报错。
### 2. 示例2如下
```Rust
fn main() {
let x = 5; // ----------+-- 'b
@@ -46,7 +54,7 @@ fn main() {
// --+ |
} // ----------+
```
对于上面的代码借用检查器将x的生命周期标注为'b将r的生命周期标注为'a,比较两者范围,发现'b > 'a被引用对象比它的应用者存在的时间长编译检查通过。
对于上面的代码借用检查器将x的生命周期标注为`'b`,将`r`的生命周期标注为`'a`,比较两者范围,发现`'b > 'a`,被引用对象比它的应用者存在的时间长,编译检查通过。
## 3.11.3 编译器有时无法自动推导生命周期
@@ -66,15 +74,19 @@ fn main() {
let r = longest(s1.as_str(), s2.as_str());
}
```
原因为:***在存在多个引用时,编译器有时会无法自动推导生命周期,此时就需要程序员在代码中手动去标注,通过为参数标注合适的生命周期来帮助编译器进行借用检查的分析***。
## 3.11.4 标注生命周期
### 1. 生命周期标注的语法
在开始说明生命周期标注语法前,需要特别明确的是:生命周期标注并不会改变任何引用的实际作用域,标记的生命周期只是为了取悦编译器,让编译器不要难为代码。
生命周期标注的语法为:生命周期参数名称必须以撇号'开头,其名称通常全是小写,类似于泛型,其名称非常短。比较常见的是使用'a作为第一个生命周期标注。生命周期参数标注位于引用符号&之后,并有一个空格来将引用类型与生命周期注解分隔开。
生命周期标注的语法为:生命周期参数名称必须以撇号`'`开头,其名称通常全是小写,类似于泛型,其名称非常短。比较常见的是使用`'a`作为第一个生命周期标注。生命周期参数标注位于引用符号`&`之后,并有一个空格来将引用类型与生命周期注解分隔开。
下面为生命周期标注的例子:
```Rust
&i32 // 引用
&'a i32 // 带有显式生命周期的引用
@@ -82,7 +94,9 @@ fn main() {
```
### 2. 函数签名中的生命周期
对于3.11.3例子中的函数,显式标注生命周期后的代码如下:
```Rust
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
@@ -92,10 +106,13 @@ fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
}
}
```
上面代码中对x和y标注生命周期为'a返回的引用的生命周期也为'a。当调用这个函数时就要求传入的x和y的生命周期必须是大于等于'a的。当不遵守这个规则的参数传入时借用检查器就会报错。
上面代码中对x和y标注生命周期为`'a`,返回的引用的生命周期也为`'a`。当调用这个函数时,就要求传入的`x``y`的生命周期必须是大于等于`'a`的。当不遵守这个规则的参数传入时,借用检查器就会报错。
### 3. 深入思考生命周期标注
1指定生命周期参数的正确方式依赖函数实现的具体功能。如下代码中将不用标注y的生命周期因为返回值不依赖于y的生命周期。
1指定生命周期参数的正确方式依赖函数实现的具体功能。如下代码中将不用标注`y`的生命周期因为返回值不依赖于y的生命周期。
```Rust
fn longest<'a>(x: &'a str, y: &str) -> &'a str {
x
@@ -103,6 +120,7 @@ fn longest<'a>(x: &'a str, y: &str) -> &'a str {
```
2当从函数返回一个引用返回值的生命周期参数需要与一个参数的生命周期参数相匹配。如果返回的引用没有指向任何一个参数那么唯一的可能就是它指向一个函数内部创建的值它将会是一个悬垂引用。如下代码将编译错误
```Rust
fn longest<'a>(x: &str, y: &str) -> &'a str {
let result = String::from("really long string");
@@ -111,7 +129,9 @@ fn longest<'a>(x: &str, y: &str) -> &'a str {
```
### 4. 结构体中的生命周期
结构体中的生命周期标注示例如下:
```Rust
#[derive(Debug)]
struct A<'a> {
@@ -126,12 +146,15 @@ fn main() {
```
### 5. 生命周期省略
在大多数情况下,程序员不用在代码中显式标注生命周期,因为编译器能自动推导。不标注生命周期,我们称之为生命周期省略。例如下面的代码可以正确编译:
```Rust
fn get_s(s: &str) -> &str {
s
}
```
关于生命周期省略有如下说明:
1遵守生命周期省略规则的情况下能明确变量的生命周期则无需明确指定生命周期。函数或者方法的参数的生命周期称为输入生命周期而返回值的生命周期称为输出生命周期。
@@ -142,20 +165,21 @@ fn get_s(s: &str) -> &str {
三条判断规则如下:
a、每个引用的参数都有它自己的生命周期参数。例如如下
一个引用参数的函数,其中有一个生命周期: ```fn foo<'a>(x: &'a i32)```
两个引用参数的函数,则有两个生命周期 ```fn foo<'a, 'b>(x: &'a i32, y: &'b i32)```
以此类推。
b、如果只有一个输入生命周期参数那么它被赋予所有输出生命周期参数
```Rust
fn foo(x: &i32) -> &i32 等价于 fn foo<'a>(x: &'a i32) -> &'a i32
```
c、如果方法有多个输入生命周期参数不过其中之一因为方法的缘故为```&self```或者```&mut self```,那么```self```的生命周期被赋予所有输出生命周期参数。
a、每个引用的参数都有它自己的生命周期参数。例如如下
一个引用参数的函数,其中有一个生命周期: `fn foo<'a>(x: &'a i32)`
两个引用参数的函数,则有两个生命周期 `fn foo<'a, 'b>(x: &'a i32, y: &'b i32)`以此类推。
b、如果只有一个输入生命周期参数那么它被赋予所有输出生命周期参数
```Rust
fn foo(x: &i32) -> &i32 fn foo<'a>(x: &'a i32) -> &'a i32
```
c、如果方法有多个输入生命周期参数不过其中之一因为方法的缘故为`&self`或者`&mut self`,那么`self`的生命周期被赋予所有输出生命周期参数。
### 6. 方法中的生命周期
结构体字段的生命周期必须总是在impl关键字之后声明并在结构体名称之后使用这些声明周期是结构体类型的一部分示例如下
```Rust
struct StuA<'a> {
name: &'a str,
@@ -166,7 +190,9 @@ impl<'a> StuA<'a> {
}
}
```
下面的例子中,其方法没有显式标注生命周期,因为它符合生命周期省略规则中的第三条规则,代码如下:
```Rust
struct StuA<'a> {
name: &'a str,
@@ -179,13 +205,17 @@ impl<'a> StuA<'a> {
```
### 7. 静态生命周期
静态生命周期定义方式为:```'static```,其生命周期存活于整个程序期间。所有的字符字面值都拥有```'static```生命周期,代码中可以如下来标注:
静态生命周期定义方式为:`'static`,其生命周期存活于整个程序期间。所有的字符字面值都拥有`'static`生命周期,代码中可以如下来标注:
```Rust
let s: &'static str = "I have a static filetime";
```
### 8. 结合泛型参数、trait bounds和生命周期的例子
下面示例为在同一函数中指定泛型类型参数、trait bounds 和生命周期:
```Rust
use std::fmt::Display;
fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str

View File

@@ -1,15 +1,18 @@
# 3.12 错误处理
Rust将错误分为两大类可恢复的和不可恢复的错误。
- 可恢复错误通常代表向用户报告错误和重试操作是合理的情况例如未找到文件。rust中使用```Result```来处理可恢复错误。
- 不可恢复错误是bug的同义词如尝试访问超过数组结尾的位置。rust中通过```panic```来实现。
## 3.12.1 用panic!处理不可恢复错误
panic的使用方式如下
`panic!`的使用方式如下:
```Rust
fn main() {
panic!("crash and burn");
}
```
运行该程序会打印如下错误:
![注释](../../assets/16.png)
@@ -19,18 +22,24 @@ fn main() {
![注释](../../assets/17.png)
## 3.12.2 用Result处理可恢复错误
### 1. Result的定义
Rust中使用```Result```类型处理可恢复错误,其定义如下:
```Rust
enum Result<T, E> {
Ok(T),
Err(E),
}
```
```T```和```E```是泛型类型参数,```T```代表成功是返回的```Ok```成员中的数据类型,```E```代表失败是返回的```Err```成员中的错误的类型。
### 2. 使用Result
使用示例如下:
```Rust
use std::fs::File;
fn main() {
@@ -45,7 +54,9 @@ fn main() {
};
}
```
第3行返回的结果就是一个```Result```类型,可以使用```match```匹配```Result```的具体类型。下面为使用```str```作为```Result<T, E>```中的错误```E```的例子:
```Rust
// 该函数返回结果为Result<T, E>其中T为E为具有静态生命周期的&str类型
fn produce_error(switch: bool) -> Result<(), &'static str> {
@@ -70,9 +81,11 @@ fn main() {
```
### 3. 失败时直接panic的简写
对于返回```Result```类型的函数,不用总是使用```match```去匹配结果,还可以使用简写获取到```Result<T, E>```中的```T```类型,不过使用简写时,当```Result<T, E>```是```Err```时程序会panic
- 使用unwrap简写
#### 3.1 使用unwrap简写
```Rust
use std::fs::File;
fn main() {
@@ -81,7 +94,8 @@ fn main() {
}
```
- 使用except简写
#### 3.2 使用except简写
```Rust
use std::fs::File;
@@ -92,8 +106,11 @@ fn main() {
.expect("hello.txt should be included in this project");
}
```
### 4. 传播错误
除了函数中处理错误外,还可以选择让调用者知道这个错误并决定如何处理,这叫做传播错误。示例如下:
```Rust
fn produce_error(switch: bool) -> Result<(), &'static str> {
if switch {
@@ -123,9 +140,11 @@ fn main() {
}
}
```
在上面的代码中,```transmit_error```中就没有自己处理错误,而是选择将错误传递给外层。
传播错误可以用``````进行简写,上面的```transmit_error```函数代码用简写方式示例如下:
```Rust
fn produce_error(switch: bool) -> Result<(), &'static str> {
if switch {
@@ -144,16 +163,18 @@ fn transmit_error(flag: bool) -> Result<String, &'static str> {
fn main() {
// 下面的调用将不会打印"如果produce_error函数返回的是错误将不会执行到这里"
let result1 = transmit_error(true);
println!("+++++++++++++++++++++++++++++++++++++++++++++++");
// 下面的调用将会打印"如果produce_error函数返回的是错误将不会执行到这里"
let result2 = transmit_error(false);
let result2 = transmit_error(false);
...
}
```
下面是更复杂的简写:
```Rust
use std::io;
use std::io::Read;
@@ -161,13 +182,15 @@ use std::fs::File;
fn read_username_from_file() -> Result {
let mut s = String::new();
//如果open()失败将直接把“打开失败错误”返回如果read_to_string失败也将把“读取失败错误”返回
File::open("hello.txt")?.read_to_string(&mut s)?;
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}
```
``````运算符被用于返回```Result```的函数,```Result```返回的是```Err```类型,则直接结束将错误传播到上一级。
## 3.12.3 什么时候使用panic
关于什么时候使用panic什么使用Result总结如下
- 示例、代码原型和测试适合使用panic使用Result可以使用unwrap、expect的方式

View File

@@ -34,6 +34,7 @@ fn main() {
## 3.13.2 闭包捕获环境
下面的示例展示了闭包捕获环境中的变量:
```rust
fn main() {
let x = 4;

View File

@@ -64,7 +64,8 @@ fn main() {
- `iter_mut()`方法,创建一个在`&mut T`上进行迭代的迭代器,即集合自身可变引用上迭代的迭代器;
- `into_iter()`方法,创建一个在`T`上迭代的迭代器,即移动了自身所有权的迭代器。
Vec就实现了IntoIterator trait所以它可以转换为迭代器使用示例如下
Vec就实现了`IntoIterator trait`,所以它可以转换为迭代器,使用示例如下:
```rust
fn main() {
// 1、----使用iter()示例------
@@ -105,7 +106,8 @@ fn main() {
## 3.14.3 迭代器消费器
迭代器通过next方法来消费一个项。下面为直接使用next方法的示例
迭代器通过`next`方法来消费一个项。下面为直接使用`next`方法的示例:
```rust
fn main() {
let v1 = vec![1, 2, 3];
@@ -116,8 +118,10 @@ fn main() {
}
```
`Iterator trait`有一系列由标准库提供的默认实现的方法有些方法调用了next方法这些调用next方法的方法被称为消费适配器。
下面是一个使用消费是适配器sum方法的例子
`Iterator trait`有一系列由标准库提供的默认实现的方法,有些方法调用了`next`方法,这些调用`next`方法的方法被称为消费适配器。
下面是一个使用消费是适配器`sum`方法的例子:
```rust
fn main() {
let v1 = vec![1, 2, 3];
@@ -130,6 +134,7 @@ fn main() {
## 3.14.4 迭代器适配器
`Iterator trait`中定义了一类方法,可以把将当前迭代器变为不同类型的迭代器,这类方法就是迭代器适配器。
```rust
fn main() {
let v1: Vec<i32> = vec![1, 2, 3];
@@ -139,7 +144,9 @@ fn main() {
println!("total = {:?}", v2);
}
```
下面的代码也是使用迭代器适配器:
```rust
fn main() {
let v1: Vec<i32> = vec![1, 11, 5, 34, 2, 10];
@@ -152,6 +159,7 @@ fn main() {
## 3.14.5 自定义迭代器
自定义迭代器示例如下:
```rust
struct Counter {
count: u32,

View File

@@ -4,13 +4,13 @@ Rust 中的 Vector向量是一个动态的、可增长的数组它可
## 1. 创建 Vector
可以使用 Vec<T> 类型创建一个向量,其中 T 是存储在向量中的元素的类型。要创建一个新的空向量,可以使用 Vec::new() 方法。
可以使用 `Vec<T>` 类型创建一个向量,其中 T 是存储在向量中的元素的类型。要创建一个新的空向量,可以使用 `Vec::new()` 方法。
```rust
let mut vec = Vec::new();
```
或者,可以使用宏 vec![] 初始化一个包含初始值的向量:
或者,可以使用宏 `vec![]` 初始化一个包含初始值的向量:
```rust
let vec = vec![1, 2, 3, 4, 5];
@@ -18,7 +18,7 @@ let vec = vec![1, 2, 3, 4, 5];
## 2. 添加元素:
可以使用 push() 方法向向量的末尾添加一个元素。
可以使用 `push()` 方法向向量的末尾添加一个元素。
```rust
vec.push(6);
```
@@ -31,7 +31,7 @@ vec.push(6);
let first = vec[0]; // 访问向量中的第一个元素
```
或者,可以使用 get() 方法安全地访问元素,它返回一个 Option 类型。如果指定的索引有效,则返回 Some(element),否则返回 None。
或者,可以使用 `get()` 方法安全地访问元素,它返回一个 `Option` 类型。如果指定的索引有效,则返回 `Some(element)`,否则返回 `None`
```rust
let first = vec.get(0); // 返回 Option<&T>
@@ -39,7 +39,7 @@ let first = vec.get(0); // 返回 Option<&T>
## 4. 遍历元素:
可以使用 for 循环遍历向量中的所有元素。
可以使用 `for` 循环遍历向量中的所有元素。
```rust
for element in &vec {
@@ -49,7 +49,7 @@ for element in &vec {
## 5. 删除元素:
可以使用 remove() 方法删除向量中指定索引处的元素。此操作将删除元素并将后续元素向前移动。
可以使用 `remove()` 方法删除向量中指定索引处的元素。此操作将删除元素并将后续元素向前移动。
```rust
vec.remove(0); // 删除向量中的第一个元素
@@ -57,7 +57,7 @@ vec.remove(0); // 删除向量中的第一个元素
## 6. Vector 的容量和长度:
向量的长度是其中的元素数量,而容量是为这些元素分配的内存空间。向量会根据需要自动增长,但当长度超过容量时,它需要重新分配内存并复制元素到新内存区域。可以使用 len() 获取向量的长度,使用 capacity() 获取容量,还可以使用 shrink_to_fit() 方法减小容量以匹配当前长度。
向量的长度是其中的元素数量,而容量是为这些元素分配的内存空间。向量会根据需要自动增长,但当长度超过容量时,它需要重新分配内存并复制元素到新内存区域。可以使用 `len()` 获取向量的长度,使用 `capacity()` 获取容量,还可以使用 `shrink_to_fit()` 方法减小容量以匹配当前长度。
## 7. 切片:

View File

@@ -1,53 +1,63 @@
# 3.14.2 String字符串
Rust 中的 String字符串是一个可增长的、UTF-8 编码的字符串类型。它可以存储和处理文本数据。这里是关于 Rust 字符串的一些详细信息:
Rust 中的 String字符串是一个可增长的、UTF-8 编码的字符串类型。它可以存储和处理文本数据。这里是关于 Rust 字符串的一些详细信息:
## 1. 创建 String
可以使用 String::new() 创建一个新的空字符串,或者使用 String::from() 从字符串字面量创建字符串。
可以使用 `String::new()` 创建一个新的空字符串,或者使用 `String::from()` 从字符串字面量创建字符串。
```rust
let mut s = String::new();
let s = String::from("hello");
```
还可以使用 to_string() 方法将基本类型转换为字符串。
还可以使用 `to_string()` 方法将基本类型转换为字符串。
```rust
let num = 42;
let s = num.to_string();
```
## 2. 字符串长度:
字符串的长度是其 UTF-8 编码的字节的数量,而不是 Unicode 字符的数量。可以使用 len() 方法获取字符串的长度。
字符串的长度是其 UTF-8 编码的字节的数量,而不是 Unicode 字符的数量。可以使用 `len()` 方法获取字符串的长度。
```rust
let len = s.len();
```
## 3. 字符串拼接:
有多种方法可以将字符串拼接在一起。例如,可以使用 + 运算符或 format! 宏。
有多种方法可以将字符串拼接在一起。例如,可以使用 `+` 运算符或 `format!` 宏。
```rust
let s1 = String::from("hello");
let s2 = String::from("world");
let s3 = s1 + " " + &s2;
```
或者使用 push_str() 方法将字符串附加到现有字符串。
或者使用 `push_str()` 方法将字符串附加到现有字符串。
```rust
let mut s = String::from("hello");
s.push_str(" world");
```
## 4. 访问字符:
由于 Rust 字符串是 UTF-8 编码的,不能直接使用索引访问单个字符。但是,可以使用迭代器遍历字符串中的字符。
```rust
for c in s.chars() {
println!("{}", c);
}
```
还可以使用 bytes() 方法遍历字节,或者使用 char_indices() 方法获取字符及其对应的字节索引。
还可以使用 `bytes()` 方法遍历字节,或者使用 `char_indices()` 方法获取字符及其对应的字节索引。
## 5.字符串切片:
可以使用范围语法创建字符串的切片,它表示原始字符串中一部分的引用。需要确保范围边界位于有效的 UTF-8 字符边界上,否则将导致运行时错误。
```rust
let s = String::from("hello");
let slice = &s[0..4]; // 获取字符串前 4 个字节的切片
@@ -55,7 +65,7 @@ let slice = &s[0..4]; // 获取字符串前 4 个字节的切片
## 6. 修改字符串:
可以使用 push() 方法将一个字符添加到字符串的末尾,或者使用 insert()
可以使用 `push()` 方法将一个字符添加到字符串的末尾,或者使用 `insert()`
方法将字符插入指定的字节位置。
```rust
@@ -64,11 +74,12 @@ s.push('!');
s.insert(5, ',');
```
注意,字符串插入操作可能需要 O(n) 时间,其中 n 是插入位置之后的字节数。
注意,字符串插入操作可能需要 `O(n)` 时间,其中 n 是插入位置之后的字节数。
## 7. 删除字符串中的字符:
可以使用 pop() 方法删除并返回字符串末尾的字符,或者使用 remove() 方法删除并返回指定字节位置处的字符。
可以使用 `pop()` 方法删除并返回字符串末尾的字符,或者使用 `remove()` 方法删除并返回指定字节位置处的字符。
```rust
let mut s = String::from("hello");
s.pop();

View File

@@ -5,7 +5,8 @@ Rust 中的 HashMap哈希映射是一个基于键值对的无序集合
## 1. 创建 HashMap
要创建一个新的空 HashMap可以使用 HashMap::new() 方法。需要导入 std::collections::HashMap 模块以使用 HashMap。
要创建一个新的空 HashMap可以使用 `HashMap::new()` 方法。需要导入 `std::collections::HashMap` 模块以使用 HashMap。
```rust
use std::collections::HashMap;
@@ -15,7 +16,7 @@ let mut map = HashMap::new();
## 2. 插入键值对:
可以使用 insert() 方法向 HashMap 中添加键值对。如果使用相同的键插入新值,旧值将被替换。
可以使用 `insert()` 方法向 HashMap 中添加键值对。如果使用相同的键插入新值,旧值将被替换。
```rust
map.insert("one", 1);
map.insert("two", 2);
@@ -23,16 +24,18 @@ map.insert("two", 2);
## 3. 访问值:
可以使用 get() 方法根据键查找值。此方法返回一个 Option<&V> 类型,如果找到键,则返回 Some(&value),否则返回 None。
可以使用 `get()` 方法根据键查找值。此方法返回一个 `Option<&V>` 类型,如果找到键,则返回 `Some(&value)`,否则返回 `None`
```rust
let value = map.get("one"); // 返回 Option<&V>
```
还可以使用 get_mut() 方法获取可变引用。
还可以使用 `get_mut()` 方法获取可变引用。
## 4. 遍历键值对:
可以使用 for 循环遍历 HashMap 中的所有键值对。
可以使用 `for` 循环遍历 HashMap 中的所有键值对。
```rust
for (key, value) in &map {
println!("{}: {}", key, value);
@@ -40,28 +43,28 @@ for (key, value) in &map {
```
## 5. 删除键值对:
可以使用 remove() 方法根据键删除键值对。此方法返回一个 Option<V> 类型,如果找到并删除了键值对,则返回 Some(value),否则返回 None。
可以使用 `remove()` 方法根据键删除键值对。此方法返回一个 `Option<V>` 类型,如果找到并删除了键值对,则返回 `Some(value)`,否则返回 `None`
```rust
map.remove("one"); // 删除键为 "one" 的键值对
```
## 6. 检查键是否存在:
可以使用 contains_key() 方法检查 HashMap 中是否存在指定的键。
可以使用 `contains_key()` 方法检查 HashMap 中是否存在指定的键。
```rust
let has_key = map.contains_key("one"); // 返回布尔值
```
## 7. 更新值:
可以使用 entry() 方法与 or_insert() 方法结合,更新 HashMap 中的值或插入新值。
可以使用 `entry()` 方法与 `or_insert()` 方法结合,更新 HashMap 中的值或插入新值。
```rust
*map.entry("three").or_insert(3) += 1;
```
## 8. HashMap 的容量和长度:
可以使用 len() 方法获取 HashMap 中的键值对数量,使用 capacity() 方法获取容量。还可以使用 shrink_to_fit() 方法减小容量以匹配当前长度。
可以使用 `len()` 方法获取 HashMap 中的键值对数量,使用 `capacity()` 方法获取容量。还可以使用 `shrink_to_fit()` 方法减小容量以匹配当前长度。
## 9. 默认哈希器:
@@ -79,11 +82,11 @@ map.insert("one", 1);
map.insert("two", 2);
```
在这个例子中,我们使用了 twox_hash 库中的 XxHash64 哈希函数,它通常比默认的 SipHash 更快。请注意,使用自定义哈希函数可能会降低安全性,因此要确保在明确了解潜在风险的情况下进行更改。
在这个例子中,我们使用了 `twox_hash` 库中的 `XxHash64` 哈希函数,它通常比默认的 `SipHash` 更快。请注意,使用自定义哈希函数可能会降低安全性,因此要确保在明确了解潜在风险的情况下进行更改。
## 10. 合并两个 HashMap
可以使用 extend() 方法将另一个 HashMap 的键值对添加到当前 HashMap 中。如果存在重复的键,目标 HashMap 中的值将被源 HashMap 中的值覆盖。
可以使用 `extend()` 方法将另一个 HashMap 的键值对添加到当前 HashMap 中。如果存在重复的键,目标 HashMap 中的值将被源 HashMap 中的值覆盖。
```rust
let mut map1 = HashMap::new();
@@ -97,6 +100,6 @@ map2.insert("three", 3);
map1.extend(map2); // 将 map2 中的键值对添加到 map1
```
这样map1 将包含键值对 "one" -> 1, "two" -> 22 和 "three" -> 3。
这样map1 将包含键值对 `"one" -> 1`, `"two" -> 22``"three" -> 3`
总之Rust 中的 HashMap 是一个功能丰富且性能优越的键值对集合,非常适合在需要快速查找和修改操作的场景中使用。

View File

@@ -4,7 +4,8 @@ Rust 中的 HashSet哈希集合是一种无序的、不含重复元素的
## 1. 创建 HashSet
要创建一个新的空 HashSet可以使用 HashSet::new() 方法。需要导入 std::collections::HashSet 模块以使用 HashSet。
要创建一个新的空 HashSet可以使用 `HashSet::new()` 方法。需要导入 `std::collections::HashSet` 模块以使用 HashSet。
```rust
use std::collections::HashSet;
@@ -13,7 +14,7 @@ let mut set = HashSet::new();
## 2. 添加元素:
可以使用 insert() 方法向 HashSet 中添加元素。如果元素已存在,则此方法将返回 false否则返回 true。
可以使用 `insert()` 方法向 HashSet 中添加元素。如果元素已存在,则此方法将返回 `false`,否则返回 `true`
```rust
set.insert(1);
@@ -23,14 +24,16 @@ set.insert(3);
## 3. 检查元素是否存在:
可以使用 contains() 方法检查 HashSet 中是否存在指定的元素。
可以使用 `contains()` 方法检查 HashSet 中是否存在指定的元素。
```rust
let contains = set.contains(&1); // 返回布尔值
```
## 4. 删除元素:
可以使用 remove() 方法删除 HashSet 中的元素。此方法返回一个 bool 类型,如果找到并删除了元素,则返回 true否则返回 false。
可以使用 `remove()` 方法删除 HashSet 中的元素。此方法返回一个 bool 类型,如果找到并删除了元素,则返回 `true`,否则返回 `false`
```rust
set.remove(&1); // 删除元素 1
```
@@ -46,7 +49,7 @@ for element in &set {
## 6. HashSet 的长度:
可以使用 len() 方法获取 HashSet 中的元素数量。还可以使用 is_empty() 方法检查 HashSet 是否为空。
可以使用 `len()` 方法获取 HashSet 中的元素数量。还可以使用 `is_empty()` 方法检查 HashSet 是否为空。
## 7. 集合操作:
@@ -67,26 +70,29 @@ HashSet 支持一些基本的集合操作,如并集、交集、差集和对称
```
- 差集difference返回一个新的 HashSet包含第一个集合中存在但第二个集合中不存在的元素。
```rust
let difference: HashSet<_> = set1.difference(&set2).cloned().collect();
```
- 对称差集symmetric_difference返回一个新的 HashSet包含两个集合中唯一的元素也就是只存在于一个集合中的元素
```rust
let symmetric_difference: HashSet<_> = set1.symmetric_difference(&set2).cloned().collect();
```
## 8. 清空 HashSet
可以使用 clear() 方法删除 HashSet 中的所有元素。
可以使用 `clear()` 方法删除 HashSet 中的所有元素。
```rust
set.clear(); // 清空 HashSet
```
总的来说Rust 中的 HashSet提供了一种高效且易于使用的无序集合实现适用于需要快速查找、添加和删除操作的场景。由于 HashSet 的底层实现基于哈希表,它能够在大部分情况下为这些操作提供 O(1) 的平均时间复杂度。
总的来说Rust 中的 HashSet提供了一种高效且易于使用的无序集合实现适用于需要快速查找、添加和删除操作的场景。由于 HashSet 的底层实现基于哈希表,它能够在大部分情况下为这些操作提供 O(1) 的平均时间复杂度。
与 HashMap 类似Rust 的 HashSet 默认使用一个加密安全的哈希函数SipHash以防止哈希碰撞攻击。如果需要更高的性能可以考虑使用自定义哈希器但要确保在明确了解潜在风险的情况下进行更改。这是一个使用自定义哈希器的例子
与 HashMap 类似Rust 的 HashSet 默认使用一个加密安全的哈希函数SipHash以防止哈希碰撞攻击。如果需要更高的性能可以考虑使用自定义哈希器但要确保在明确了解潜在风险的情况下进行更改。这是一个使用自定义哈希器的例子
```rust
use std::collections::HashSet;
@@ -100,4 +106,4 @@ set.insert(1);
set.insert(2);
```
在这个例子中,我们使用了 twox_hash 库中的 XxHash64 哈希函数,它通常比默认的 SipHash 更快。
在这个例子中,我们使用了 `twox_hash` 库中的 `XxHash64` 哈希函数,它通常比默认的 SipHash 更快。

View File

@@ -5,7 +5,7 @@ Rust 中的 LinkedList链表是一种线性数据结构它由一系列
## 1. 创建 LinkedList
要创建一个新的空 LinkedList可以使用 LinkedList::new() 方法。需要导入 std::collections::LinkedList 模块以使用 LinkedList。
要创建一个新的空 LinkedList可以使用 `LinkedList::new()` 方法。需要导入 `std::collections::LinkedList` 模块以使用 LinkedList。
```rust
use std::collections::LinkedList;
@@ -14,7 +14,7 @@ let mut list = LinkedList::new();
## 2. 添加元素:
可以使用 push_front() 和 push_back() 方法将元素添加到链表的开头和结尾。
可以使用 `push_front()``push_back()` 方法将元素添加到链表的开头和结尾。
```rust
list.push_front(1);
list.push_back(2);
@@ -22,7 +22,7 @@ list.push_back(2);
## 3. 访问元素:
可以使用 front() 和 back() 方法分别访问链表的第一个和最后一个元素。这些方法返回一个 Option<&T> 类型,如果链表不为空,则返回 Some(&element),否则返回 None。
可以使用 `front()``back()` 方法分别访问链表的第一个和最后一个元素。这些方法返回一个 `Option<&T>` 类型,如果链表不为空,则返回 `Some(&element)`,否则返回 `None`
```rust
let first_element = list.front(); // 返回 Option<&T>
@@ -33,7 +33,8 @@ let last_element = list.back(); // 返回 Option<&T>
## 4. 删除元素:
可以使用 pop_front() 和 pop_back() 方法分别删除并返回链表的第一个和最后一个元素。这些方法返回一个 Option<T> 类型,如果链表不为空且成功删除元素,则返回 Some(element),否则返回 None。
可以使用 `pop_front()``pop_back()` 方法分别删除并返回链表的第一个和最后一个元素。这些方法返回一个 `Option<T>` 类型,如果链表不为空且成功删除元素,则返回 `Some(element)`,否则返回 `None`
```rust
list.pop_front(); // 删除并返回第一个元素
list.pop_back(); // 删除并返回最后一个元素
@@ -41,7 +42,8 @@ list.pop_back(); // 删除并返回最后一个元素
## 5. 遍历元素:
可以使用 iter() 方法遍历链表中的所有元素。iter_mut() 方法可用于遍历可变引用。
可以使用 `iter()` 方法遍历链表中的所有元素。`iter_mut()` 方法可用于遍历可变引用。
```rust
for element in list.iter() {
println!("{}", element);
@@ -50,11 +52,11 @@ for element in list.iter() {
## 6. 链表长度:
可以使用 len() 方法获取链表中的元素数量。还可以使用 is_empty() 方法检查链表是否为空。
可以使用 `len()` 方法获取链表中的元素数量。还可以使用 `is_empty()` 方法检查链表是否为空。
## 7. 清空链表:
可以使用 clear() 方法删除链表中的所有元素。
可以使用 `clear()` 方法删除链表中的所有元素。
```rust
list.clear(); // 清空链表
@@ -62,7 +64,7 @@ list.clear(); // 清空链表
## 8. 分割链表:
可以使用 split_off() 方法在指定索引处分割链表。此操作会将链表分成两个链表,前一个链表包含指定索引之前的元素,后一个链表包含指定索引及之后的元素。
可以使用 `split_off()` 方法在指定索引处分割链表。此操作会将链表分成两个链表,前一个链表包含指定索引之前的元素,后一个链表包含指定索引及之后的元素。
```rust
let mut list1 = LinkedList::new();

View File

@@ -4,7 +4,7 @@ Rust 中的 BTreeMapB 树映射)是一种自平衡的有序映射数据结
## 1. 创建 BTreeMap
要创建一个新的空 BTreeMap可以使用 BTreeMap::new() 方法。需要导入 std::collections::BTreeMap 模块以使用 BTreeMap。
要创建一个新的空 BTreeMap可以使用 `BTreeMap::new()` 方法。需要导入 `std::collections::BTreeMap` 模块以使用 BTreeMap。
```rust
use std::collections::BTreeMap;
@@ -14,7 +14,8 @@ let mut map = BTreeMap::new();
## 2. 添加元素:
可以使用 insert() 方法向 BTreeMap 中添加键值对。如果键已存在,则此方法将返回 Some(old_value),否则返回 None。
可以使用 `insert()` 方法向 BTreeMap 中添加键值对。如果键已存在,则此方法将返回 `Some(old_value)`,否则返回 `None`
```rust
map.insert("one", 1);
map.insert("two", 2);
@@ -23,17 +24,17 @@ map.insert("three", 3);
## 3. 访问元素:
可以使用 get() 方法根据键查找对应的值。此方法返回一个 Option<&V> 类型,如果找到键,则返回 Some(&value),否则返回 None。
可以使用 `get()` 方法根据键查找对应的值。此方法返回一个 `Option<&V>` 类型,如果找到键,则返回 `Some(&value)`,否则返回 `None`
```rust
let value = map.get("one"); // 返回 Option<&V>
```
还可以使用 get_mut() 方法获取可变引用。
还可以使用 `get_mut()` 方法获取可变引用。
## 4. 删除元素:
可以使用 remove() 方法删除 BTreeMap 中的键值对。此方法返回一个 Option<V> 类型,如果找到并删除了键值对,则返回 Some(value),否则返回 None。
可以使用`remove()` 方法删除 BTreeMap 中的键值对。此方法返回一个 `Option<V>` 类型,如果找到并删除了键值对,则返回 `Some(value)`,否则返回 `None`
```rust
map.remove("one"); // 删除键为 "one" 的键值对
@@ -41,7 +42,8 @@ map.remove("one"); // 删除键为 "one" 的键值对
## 5. 遍历元素:
可以使用 iter() 方法遍历 BTreeMap 中的所有键值对。遍历顺序按键的顺序进行。iter_mut() 方法可用于遍历可变引用。
可以使用 `iter()` 方法遍历 BTreeMap 中的所有键值对。遍历顺序按键的顺序进行。`iter_mut()` 方法可用于遍历可变引用。
```rust
for (key, value) in map.iter() {
println!("{}: {}", key, value);
@@ -50,11 +52,11 @@ for (key, value) in map.iter() {
## 6. BTreeMap 的长度:
可以使用 len() 方法获取 BTreeMap 中的键值对数量。还可以使用 is_empty() 方法检查 BTreeMap 是否为空。
可以使用 `len()` 方法获取 BTreeMap 中的键值对数量。还可以使用 `is_empty()` 方法检查 BTreeMap 是否为空。
## 7. 最小和最大键:
可以使用 first_key_value() 和 last_key_value() 方法分别获取 BTreeMap 中具有最小和最大键的键值对。这些方法返回一个 Option<(&K, &V)> 类型,如果找到键值对,则返回 Some((&key, &value)),否则返回 None。
可以使用 `first_key_value()``last_key_value()` 方法分别获取 BTreeMap 中具有最小和最大键的键值对。这些方法返回一个 `Option<(&K, &V)>` 类型,如果找到键值对,则返回 `Some((&key, &value))`,否则返回 None。
```rust
let min_key_value = map.first_key_value(); // 返回 Option<(&K, &V)>
@@ -63,7 +65,8 @@ let max_key_value = map.last_key_value(); // 返回 Option<(&K, &V)>
## 8. 范围查询:
可以使用 range() 方法查询 BTreeMap 中某个范围内的键值对。例如,可以查询所有键大于等于 "one" 且小于等于 "three" 的键值对:
可以使用 `range()` 方法查询 BTreeMap 中某个范围内的键值对。例如,可以查询所有键大于等于 "one" 且小于等于 "three" 的键值对:
```rust
for (key, value) in map.range("one".."three") {
println!("{}: {}", key, value);

View File

@@ -4,7 +4,8 @@ Rust 中的 BTreeSetB 树集合)是一种自平衡的有序集合数据结
## 1. 创建 BTreeSet
要创建一个新的空 BTreeSet可以使用 BTreeSet::new() 方法。需要导入 std::collections::BTreeSet 模块以使用 BTreeSet。
要创建一个新的空 BTreeSet可以使用 `BTreeSet::new()` 方法。需要导入 `std::collections::BTreeSet` 模块以使用 BTreeSet。
```rust
use std::collections::BTreeSet;
@@ -13,7 +14,8 @@ let mut set = BTreeSet::new();
## 2. 添加元素:
可以使用 insert() 方法向 BTreeSet 中添加元素。如果元素已存在,则此方法将返回 false否则返回 true。
可以使用 `insert()` 方法向 BTreeSet 中添加元素。如果元素已存在,则此方法将返回 `false`,否则返回 `true`
```rust
set.insert(1);
set.insert(2);
@@ -22,21 +24,24 @@ set.insert(3);
## 3. 检查元素是否存在:
可以使用 contains() 方法检查 BTreeSet 中是否存在指定的元素。
可以使用 `contains()` 方法检查 BTreeSet 中是否存在指定的元素。
```rust
let contains = set.contains(&1); // 返回布尔值
```
## 4. 删除元素:
可以使用 remove() 方法删除 BTreeSet 中的元素。此方法返回一个 bool 类型,如果找到并删除了元素,则返回 true否则返回 false。
可以使用 `remove()` 方法删除 BTreeSet 中的元素。此方法返回一个 bool 类型,如果找到并删除了元素,则返回 `true`,否则返回 `false`
```rust
set.remove(&1); // 删除元素 1
```
## 5. 遍历元素:
可以使用 for 循环遍历 BTreeSet 中的所有元素。遍历顺序按元素的顺序进行。
可以使用 `for` 循环遍历 BTreeSet 中的所有元素。遍历顺序按元素的顺序进行。
```rust
for element in &set {
println!("{}", element);
@@ -45,33 +50,42 @@ for element in &set {
## 6. BTreeSet 的长度:
可以使用 len() 方法获取 BTreeSet 中的元素数量。还可以使用 is_empty() 方法检查 BTreeSet 是否为空。
可以使用 `len()` 方法获取 BTreeSet 中的元素数量。还可以使用 `is_empty()` 方法检查 BTreeSet 是否为空。
## 7. 集合操作:
BTreeSet 支持一些基本的集合操作,如并集、交集、差集和对称差集。
- 并集union返回一个新的 BTreeSet包含两个集合中的所有元素。
```rust
let set1: BTreeSet<_> = [1, 2, 3].iter().cloned().collect();
let set2: BTreeSet<_> = [3, 4, 5].iter().cloned().collect();
let union: BTreeSet<_> = set1.union(&set2).cloned().collect();
```
```rust
let set1: BTreeSet<_> = [1, 2, 3].iter().cloned().collect();
let set2: BTreeSet<_> = [3, 4, 5].iter().cloned().collect();
let union: BTreeSet<_> = set1.union(&set2).cloned().collect();
```
- 交集intersection返回一个新的 BTreeSet包含两个集合中共有的元素。
```rust
let intersection: BTreeSet<_> = set1.intersection(&set2).cloned().collect();
```
```rust
let intersection: BTreeSet<_> = set1.intersection(&set2).cloned().collect();
```
- 差集difference返回一个新的 BTreeSet包含第一个集合中存在但第二个集合中不存在的元素。
```rust
let difference: BTreeSet<_> = set1.difference(&set2).cloned().collect();
```
```rust
let difference: BTreeSet<_> = set1.difference(&set2).cloned().collect();
```
- 对称差集symmetric_difference返回一个新的 BTreeSet包含两个集合中唯一的元素也就是只存在于一个集合中的元素
```rust
let symmetric_difference: BTreeSet<_> = set1.symmetric_difference(&set2).cloned().collect();
```
```rust
let symmetric_difference: BTreeSet<_> = set1.symmetric_difference(&set2).cloned().collect();
```
## 8. 最小和最大元素:
可以使用 first() 和 last() 方法分别获取 BTreeSet 中的最小和最大元素。这些方法返回一个 Option<&T> 类型,如果找到元素,则返回 Some(&element),否则返回 None。
可以使用 `first()``last()` 方法分别获取 BTreeSet 中的最小和最大元素。这些方法返回一个 `Option<&T>` 类型,如果找到元素,则返回 `Some(&element)`,否则返回 `None`
```rust
let min_element = set.first(); // 返回 Option<&T>
let max_element = set.last(); // 返回 Option<&T>
@@ -79,7 +93,8 @@ let max_element = set.last(); // 返回 Option<&T>
## 9. 范围查询:
可以使用 range() 方法查询 BTreeSet 中某个范围内的元素。例如,可以查询所有大于等于 1 且小于等于 3 的元素:
可以使用 `range()` 方法查询 BTreeSet 中某个范围内的元素。例如,可以查询所有大于等于 1 且小于等于 3 的元素:
```rust
for element in set.range(1..=3) {
println!("{}", element);
@@ -88,7 +103,7 @@ for element in set.range(1..=3) {
## 10. 清空 BTreeSet
可以使用 clear() 方法删除 BTreeSet 中的所有元素。
可以使用 `clear()` 方法删除 BTreeSet 中的所有元素。
```rust
set.clear(); // 清空 BTreeSet

View File

@@ -1,4 +1,5 @@
# 3.16.1 智能指针介绍
指针是一个包含了内存地址的变量,该内存地址引用或者指向了另外的数据,其在内存中的示意图如下:
![注释](../../assets/18.png)

View File

@@ -1,4 +1,5 @@
# 3.16.2 Box智能指针
```Box```智能指针是Rust中最基本的在堆上分配内存的方式。定义```Box```变量,将值存放在堆上,栈上则保留指向堆数据的指针。除了数据被存储在堆上外,```Box```没有任何性能损失。
## 1. Box的基本使用方式
@@ -12,11 +13,13 @@ fn main() {
```
## 2. 使用Box的内存布局
前面示例中使用```Box```定义了变量```b```,其内存布局方式如下:
![注释](../../assets/20.png)
## 3. Box适合使用的场景
```Box```适合用于如下场景:
- 当有一个在编译时未知大小的类型,而又需要在确切大小的上下文中使用这个类型值的时候;
- 当有大量数据并希望在确保数据不被拷贝的情况下转移所有权的时候;
@@ -32,7 +35,7 @@ enum List {
//struct List{
// int data;
// struct List next;//编译报错因为编译器并不知道next有多大next又是一个List
//}
//}
Nil,
}

View File

@@ -1,7 +1,9 @@
# 3.16.3 Deref trait
# 3.16.3 Deref trait
## 1. 通过*使用引用背后的值
常规引用是一个指针类型,包含了目标数据存储的内存地址。对常规引用使用 * ,可以通过解引用的方式获取到内存地址对应的数据值,示例如下:
常规引用是一个指针类型,包含了目标数据存储的内存地址。对常规引用使用 `*` ,可以通过解引用的方式获取到内存地址对应的数据值,示例如下:
```Rust
fn main() {
let x = 5;
@@ -14,7 +16,8 @@ fn main() {
```
## 2. 通过*使用智能指针背后的值
对于智能指针,也可以通过*使用其背后的值,示例如下:
对于智能指针,也可以通过`*`使用其背后的值,示例如下:
```Rust
fn main() {
let x = 5;
@@ -26,14 +29,16 @@ fn main() {
## 3. 使用Deref
实现Deref trait允许我们重载解引用运算符*。通过为类型实现Deref trait类型可以被当做常规引用来对待。简单来说如果类型A实现了Deref trait那么就可以写如下代码
实现`Deref trait`允许我们重载解引用运算符`*`。通过为类型实现`Deref trait`类型可以被当做常规引用来对待。简单来说如果类型A实现了`Deref trait`,那么就可以写如下代码:
```Rust
let a: A = A::new();
let b = &a;
let c = *b; //对A实现了Deref trait所以可以对A类型解引用
```
下面的代码定义一个MyBox类型并为其实现Deref trait
下面的代码定义一个`MyBox`类型,并为其实现`Deref trait`
```Rust
use std::ops::Deref;
struct MyBox<T>(T);
@@ -59,13 +64,15 @@ fn main() {
```
## 4. 函数和方法的隐式Deref强制转换
对于函数和方法的传参Rust 提供了隐式转换Deref 转换。若一个类型实现了 Deref 特征,那它的引用在传给函数或方法时,会根据参数签名来决定是否进行隐式的 Deref 转换,例如:
```Rust
use std::ops::Deref;
struct MyString(String);
impl Deref for MyString { // MyString类型实现了Deref trait
type Target = str;
fn deref(&self) -> &str {
fn deref(&self) -> &str {
self.0.as_str()
}
}
@@ -76,17 +83,18 @@ fn print(s: &str) {
fn main() {
let y = MyString("Hello".to_string());
print(&y); //此处发生隐式转换,&y从&MyString自动转换为&str类型转换由&触发,过程如下:
// &y隐式转换过程: &MyString -> &str
// &y隐式转换过程: &MyString -> &str
}
```
上面代码的关键点为:
- MyString实现了Deref特征可以在需要时自动被转换为&str类型
- &y是一个&MyString类型当它被传给print函数时自动通过Deref转换成了&str
- 必须使用&y的方式来触发Deref(仅引用类型的实参才会触发自动解引用)。
- `MyString`实现了Deref特征可以在需要时自动被转换为`&str`类型;
- `&y`是一个`&MyString`类型,当它被传给`print`函数时,自动通过`Deref`转换成了`&str`
- 必须使用`&y`的方式来触发Deref(仅引用类型的实参才会触发自动解引用)。
下面为调用方法时发生的隐式自动转换的例子:
```Rust
use std::ops::Deref;
struct MyType(u32);
@@ -116,6 +124,7 @@ fn main() {
```
Deref还支持连续的隐式转换示例如下
```Rust
fn print(s: &str) {
println!("{}", s);
@@ -125,13 +134,15 @@ fn main() {
print(&s) // &s隐式转换过程&Box -> &String -> &str
}
```
上面的代码中Box、String都实现了Deref当把&s传入到print函数时发送连续隐式转换&s先从&Box转换到&String再转换到&str
上面的代码中,`Box``String`都实现了Deref当把`&s`传入到`print`函数时,发送连续隐式转换(`&s`先从`&Box`转换到`&String`,再转换到`&str`)。
## 5. Deref强制转换与可变性交互
类似于如何使用Dereftrait 重载不可变引用的*运算符Rust提供了DerefMut trait用于重载可变引用的*运算符。
类似于如何使用`Deref trait` 重载不可变引用的*运算符Rust提供了DerefMut trait用于重载可变引用的*运算符。
Rust在发现类型和 trait 实现满足三种情况时会进行 Deref 强制转换:
- 当T: Deref<Target=U>时从&T到&U
- 当T: DerefMut<Target=U>时从&mut T&mut U。
- 当 T: Deref<Target=U>时从&mut T到&U
-`T: Deref<Target=U>`时从`&T``&U`
-`T: DerefMut<Target=U>`时从`&mut T``&mut U`
-`T: Deref<Target=U>`时从`&mut T``&U`

View File

@@ -1,7 +1,11 @@
# 3.16.4 Drop trait
## 1. Drop trait
Drop trait类似于其它语言中的析构函数当值离开作用域时执行此函数的代码。可以为任何类型提供Drop trait的实现。
为一个类型实现Drop trait的示例如下:
`Drop trait`类似于其它语言中的析构函数,当值离开作用域时执行此函数的代码。可以为任何类型提供Drop trait的实现。
为一个类型实现`Drop trait`的示例如下:
```Rust
struct Dog(String);
@@ -25,7 +29,9 @@ fn main() {
在上面示例代码中并没有打印语句但是在drop方法中实现了打印可以看到当_a和_b离开作用域时自动调用了drop方法。
## 2. 通过std::mem::drop提早丢弃值
当要显示的清理值时不能直接调用Drop trait里面的drop方法而要使用std::mem::drop方法示例如下
当要显示的清理值时不能直接调用Drop trait里面的drop方法而要使用`std::mem::drop`方法,示例如下:
```Rust
struct Dog(String);
@@ -44,6 +50,7 @@ fn main() {
println!("At the end of main");
}
```
代码运行结果如下:
![注释](../../assets/25.png)

View File

@@ -1,10 +1,13 @@
# 3.16.5 Rc智能指针
## 1. 使用场景分析
假定有这样一个需求,希望创建两个共享第三个列表所有权的列表,其概念类似于如下图:
![注释](../../assets/26.png)
根据前面的知识,可能写出来的代码如下:
```Rust
enum List {
Cons(i32, Box<List>),
@@ -21,17 +24,20 @@ fn main() {
但是上面的代码报错,因为**Rust 所有权机制要求一个值只能有一个所有者**。为了解决此类需求Rust提供了Rc智能指针。
## 2. 使用Rc共享数据
Rc智能指针通过引用计数解决数据共享的问题下面是Rc使用的简单代码
```Rust
use std::rc::Rc;
fn main() {
let a = Rc::new(5u32);
let b = Rc::clone(&a);
let c = a.clone();
let c = a.clone();
}
```
上面代码中a、b、c就共享数据5。当创建b时不会获取a的所有权会克隆a所包含的Rc(5u32)这会使引用计数从1增加到2并允许a和b共享Rc(5u32)的所有权。
创建c时也会克隆 a引用计数从2增加为3。每次调用Rc::cloneRc(5u32)的引用计数都会增加,直到有零个引用之前其数据都不会被清理。其在内存中的表示如下:
创建c时也会克隆 a引用计数从2增加为3。每次调用`Rc::clone`Rc(5u32)的引用计数都会增加,直到有零个引用之前其数据都不会被清理。其在内存中的表示如下:
![注释](../../assets/27.png)
@@ -57,7 +63,9 @@ fn main() {
## 3. 打印Rc的引用计数
下面的示例打印了Rc的引用计数
```Rust
enum List {
Cons(i32, Rc<List>),
@@ -82,4 +90,3 @@ fn main() {
- 通过Rc是允许程序的多个部分之间**只读的共享数据**因为相同位置的多个可变引用可能会造成数据竞争和不一致。如果涉及到修改需要使用RefCell或者Mutex。
- Rc只能是同一个线程内部共享数据它是非线程安全的。如果要在多线程中共享需要使用Arc。

View File

@@ -1,20 +1,25 @@
# 3.16.6 RefCell智能指针
## 1. 使用RefCell
**内部可变性Interior mutability** 是Rust中的一个设计模式它允许在有不可变引用时改变数据这通常是借用规则所不允许的。RefCell正是为Rust提供内部可变性的智能指针。
在Rust中当使用mut或者&mut显示的声明一个变量或者引用时才能修改它们的值。编译器会对此严格检查。
```Rust
let mut a = 1u32;
let mut a = 1u32;
a = 2u32; // 可以修改a的值
let b = 3u32;
b = 4u32; // 报错,不允许修改
```
但是当使用RefCell时可以对其内部包含的内容进行修改如下
```Rust
use std::cell::RefCell;
fn main() {
let data = RefCell::new(1); // data本身是不可变变量
{
let mut v = data.borrow_mut();
let mut v = data.borrow_mut();
*v = 2; // 但是却可以对RefCell内部的值进行修改
}
println!("data: {:?}", data.borrow()); // 将输出为2
@@ -22,8 +27,11 @@ fn main() {
```
## 2. 使用RefCell编译器在运行时检查可变性
在上面的代码中data本身是一个不可变变量但是在代码中却可以改变它内部的值第6行将值从1改成了2这就是内部可变性。**当使用RefCell定义变量后编译器会认为在编译时这个变量是不可变的但是在运行时可以得到其可变借用从而改变其内部的值**。换句话说,**使用RefCell是运行时检查借用规则**。
下面是另一个使用RefCell的例子
```Rust
#[derive(Debug)]
enum List {
@@ -52,6 +60,7 @@ fn main() {
```
下面是一个使用RefCell时容易犯错的例子
```Rust
use std::cell::RefCell;
fn main() {
@@ -62,7 +71,10 @@ fn main() {
// 前面使用了可变引用第7行这里又使用不可变引用违背所有权规则
}
```
***分析此处需要注意的是对于RefCell的可变引用、不可变引用的作用域范围Rust 1.68.2中其定义方式还是从定义开始到花括号前结束。这和普通引用是不一样的因为在新版编译器中1.31以后),普通引用的作用域范围变成了从定义开始,到不再使用结束。因此,下面的代码是可以正确的***
> ***分析此处需要注意的是对于RefCell的可变引用、不可变引用的作用域范围Rust 1.68.2中其定义方式还是从定义开始到花括号前结束。这和普通引用是不一样的因为在新版编译器中1.31以后),普通引用的作用域范围变成了从定义开始,到不再使用结束。因此,下面的代码是可以正确的***
```Rust
fn main() {
let mut a = 5u32;
@@ -76,6 +88,7 @@ fn main() {
```
## 3. 关于Box、Rc或RefCell的选择总结
- Rc允许相同数据有多个所有者Box和RefCell有单一所有者。
- Rc允许相同数据有多个所有者`Box``RefCell`有单一所有者。
- Box允许在编译时执行不可变或可变借用检查Rc仅允许在编译时执行不可变借用检查RefCell 允许在运行时执行不可变或可变借用检查。
- 因为RefCell允许在运行时执行可变借用检查所以可以在即便RefCell自身是不可变的情况下修改其内部的值。

View File

@@ -1,9 +1,12 @@
# 3.16.7 引用循环、内存泄露、Weak智能指针
## 1. 引用循环和内存泄露
下图是一个循环链表:
![注释](../../assets/29.png)
下面的代码试图用之前学到的智能指针相关知识实现上面的链表:
```Rust
use crate::List::{Cons, Nil};
use std::cell::RefCell;
@@ -49,41 +52,45 @@ fn main() {
```
下面分析整个过程中的内存布局:
- 当执行完第26行后内存布局如下
![注释](../../assets/30.png)
![注释](../../assets/30.png)
- 在执行完第33行后b对应的Rc引用计数变成2内存布局如下
![注释](../../assets/31.png)
![注释](../../assets/31.png)
此时如果第40行代码执行将会panic因为已经成了一个循环链表Rust无法匹配到a的tail最终会造成栈溢出。
- 在最后离开作用域时Rust将会对b和a调用drop方法对b调用drop方法后内存布局如下
![注释](../../assets/32.png)
![注释](../../assets/32.png)
此时b的Rc实例引用计数减去1但是仍然不为0因为第33行让a也引用了b的Rc实例所以b所指向的内存并不会被释放。
- 然后Rust尝试drop a其对应的Rc示例引用计数减去1但仍然不为0所以调用a的drop后内存布局为
![注释](../../assets/33.png)
![注释](../../assets/33.png)
至此,造成内存泄露。
## 2. 使用弱引用Weak
Weak类似于Rc但它不持有所有权它仅仅保存一份指向数据的弱引用。弱引用就是不保证引用的对象存在如果不存在就返回一个 None。
下面为Weak和Rc的对比
| Weak | Rc |
| Weak | Rc |
| ------------------------ | ------------------------ |
| 引用不计数 | 引用计数 |
| 不拥有所有权 | 拥有所有权 |
| 不阻止值被释放 | 阻止值被释放 |
|引用的值存在返回Some不存在返回None | 引用的值必定存在 |
|通过upgrade取到Option<Rc<T>>,然后取值|通过Deref自动解引用取值无需任何操作|
对于上一节中循环链表的例子使用Weak实现为如下
```Rust
use crate::List::{Cons, Nil};
use std::cell::RefCell;
@@ -152,13 +159,14 @@ fn main() {
println!("a next item = {:?}", a.tail());
}
```
下图为上面代码的内存布局示意图:
![注释](../../assets/34.png)
![注释](../../assets/34.png)
下面再总结一下Weak的特点
- 可访问,但没有所有权,不增加引用计数,因此不会影响被引用值的释放回收;
- 可由Rc<T>调用downgrade方法转换成Weak<T>
- Weak<T>可使用upgrade方法转换成Option<Rc<T>>如果资源已经被释放则Option的值是None
- 可由`Rc<T>`调用`downgrade`方法转换成`Weak<T>`
- `Weak<T>`可使用`upgrade`方法转换成`Option<Rc<T>>`,如果资源已经被释放,则`Option`的值是`None`
- 常用于解决循环引用的问题。

View File

@@ -1,15 +1,18 @@
# 3.17.1 包、crate和模块介绍
关于三者的描述如下:
-Package是一个 Cargo 项目,它包含了一个 Cargo.toml 配置文件和一些 Rust 代码文件。一个包可以包含多个 crate并且可以有多种构建方式和配置。
- crateCrate是一个可以编译成库或二进制文件的 Rust 代码单元。每个 crate 都有一个唯一的名称,它可以被其他 crate 引用也可以被其它包中的代码引用。每个crate都有一个crate root 它是一个源文件Rust 编译器以它为起始点来构成crate根模块。对于crate来说crate root要么是src/main.rs对于二进制crate来说要么是src/lib.rs对于库crate来说
- crateCrate是一个可以编译成库或二进制文件的 Rust 代码单元。每个 crate 都有一个唯一的名称,它可以被其他 crate 引用也可以被其它包中的代码引用。每个crate都有一个crate root 它是一个源文件Rust 编译器以它为起始点来构成crate根模块。对于crate来说crate root要么是`src/main.rs`对于二进制crate来说要么是`src/lib.rs`对于库crate来说
- 模块Module是一种组织 Rust 代码的方式它可以将相关的代码放在一起形成一个代码块。模块可以嵌套可以有多个不同的访问级别public、private并且可以从其他模块中引用和访问。
三者的范围为:模块< crate < 包即一个包包含一个或多个crate一个crate可以包含多个模块。
## 1. 包含一个crate的包
下面的命令会创建一个Rust工程这个工程同时是一个crate也是一个包只有一个crate的包
```
```bash
cargo new main
```
@@ -24,8 +27,10 @@ cargo new main
可以看到这个crate的名字为main。
## 2. 包含多个crate的包
下面再创建一个稍微复杂一点的包。执行如下几条命令:
```
```bash
mkdir my-pack # 创建包文件夹
cd my-pack
cargo new my-lib --lib # 创建一个库crate
@@ -33,6 +38,7 @@ cargo new main # 创建一个可执行的二进制crate
```
然后在my-pack文件夹中添加文件Cargo.toml其内容如下
```TOML
[workspace]
@@ -41,6 +47,7 @@ members = [
"my-lib",
]
```
最后的目录层级关系如下:
![注释](../../assets/37.png)

View File

@@ -7,7 +7,7 @@ Rust有两种数据类型子集分别是标量scalar类型和复合
标量类型包括:整型、浮点型、布尔类型和字符类型。
- 整型和浮点型
### 3.2.1.1 整型和浮点型
Rust中的整型和浮点型如下
@@ -25,6 +25,7 @@ Rust中的整型和浮点型如下
上面的表格中,`f32``f64`为浮点型,其它为整型。浮点型和整型一起构成数值型。
1可以在数值字面量后面加上类型表示该类型的数值如下
```Rust
fn main(){
let _a = 33u32; // 直接加类型后缀
@@ -33,7 +34,9 @@ fn main(){
let _d = 33_f32;
}
```
2可以数值的任意位置使用下划线分割来增强可读性如下
```Rust
fn main(){
let _a = 10_000_000u32;
@@ -41,14 +44,18 @@ fn main(){
let _c = 1_33_333f32;
}
```
3当不明确指定变量的类型也不明确指定数值字面量的类型后缀时Rust默认将整数当做i32类型将浮点数当做f64类型如下
3当不明确指定变量的类型也不明确指定数值字面量的类型后缀时Rust默认将整数当做`i32`类型,将浮点数当做`f64`类型,如下:
```Rust
fn main(){
let _a = 33; // 等价于 let _a: i32 = 33; 等价于 let _a = 33i32;
let _b = 64.123; // 等价于let _b: f64 = 64.123; 等价于 let _b = 64.123f64;
}
```
4Rust使用0b表示二进制、0o表示八进制、0x表示十六进制如下
4Rust使用`0b`表示二进制、`0o`表示八进制、`0x`表示十六进制,如下:
```Rust
fn main(){
let a: u32 = 0b101; // 二进制整数
@@ -58,7 +65,9 @@ fn main(){
println!("{}, {}, {}, {}", a, b, c, d); // 5, 15, 1, 172
}
```
5Rust中所有的数值类型都支持基本数学运算加、减、乘、除、取余如下
```Rust
fn main() {
let sum = 5 + 10;
@@ -70,117 +79,129 @@ fn main() {
}
```
- 布尔型
### 3.2.1.2 布尔型
Rust中的布尔型用bool表示有两个可能的值为truefalse。布尔类型使用的场景主要是条件表达式控制流的内容使用如下
Rust中的布尔型用`bool`表示,有两个可能的值,为`true``false`。布尔类型使用的场景主要是条件表达式(控制流的内容),使用如下:
```Rust
fn main() {
// 定义方式
let a: bool = true;
let b: bool = false;
```Rust
fn main() {
// 定义方式
let a: bool = true;
let b: bool = false;
// 使用场景
if a {
println!("a is true");
} else {
println!("a is false");
}
if b {
println!("b is true");
} else {
println!("b is false");
}
// 使用场景
if a {
println!("a is true");
} else {
println!("a is false");
}
```
- 字符类型
char类型用于存放单个unicode字符占用4个字节空间。当存储char类型数据时Rust会将其转换为utf-8编码的数据存储。char字面量是单引号包含的任意单个字符字符类型使用示例如下
```Rust
fn main() {
let c: char = 'z';
let x: char = 'x';
let heart_eyed_cat: char = '😻';
if b {
println!("b is true");
} else {
println!("b is false");
}
```
}
```
### 3.2.1.3 字符类型
char类型用于存放单个unicode字符占用4个字节空间。当存储char类型数据时Rust会将其转换为utf-8编码的数据存储。`char`字面量是单引号包含的任意单个字符,字符类型使用示例如下:
```Rust
fn main() {
let c: char = 'z';
let x: char = 'x';
let heart_eyed_cat: char = '😻';
}
```
## 3.2.2. 原生复合类型
复合类型是将多个值组合成一个类型。Rust有两个原生复合类型元组和数组。
- 元组
### 3.2.2.1 元组
圆括号以及其中逗号分割的值列表组成元组,定义一个元组方式如下:
```Rust
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
}
```
可以将元组重新结构到变量上,如下:
```Rust
fn main() {
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
//接下来你可以使用x、y、z
}
```
也可以直接使用元组的元素,如下:
```Rust
fn main() {
let x: (i32, f64, u8) = (500, 6.4, 1);
let first = x.0;
let second = x.1;
let third = x.2;
}
```
圆括号以及其中逗号分割的值列表组成元组,定义一个元组方式如下:
不带任何值的元组称为unit类型单元元组可以代表空值或者空的返回类型如下
```Rust
fn main() {
let x: () = (); // 将值()保存到类型为()的变量x中
}
```
```Rust
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
}
```
- 数组
可以将元组重新结构到变量上,如下:
数组中的每个元素的类型必须相同,数组的长度是固定的,数组的定义方式如下:
```Rust
fn main() {
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
//接下来你可以使用x、y、z
}
```
```Rust
fn main() {
let a = [1, 2, 3, 4, 5]; //直接写数组的值
let b: [i32; 5] = [1, 2, 3, 4, 5]; //显示指定数组的类型和长度
let c: [i32; 5] = [3; 5]; //数组每个元素为同样的值等价于let a = [5, 5, 5, 5, 5];
}
```
数组通过索引来访问元素索引从0开始计数如下
```Rust
fn main() {
let a = [1, 2, 3, 4, 5];
也可以直接使用元组的元素,如下:
let first = a[0]; // first = 1
let second = a[1]; // second = 2
}
```
Rust中访问无效的索引元素会报错如下
```Rust
fn main() {
let a = [1, 2, 3, 4, 5];
let b = a[5]; // 错误只能放为0-4所以这个代码将无法编译
}
```
```Rust
fn main() {
let x: (i32, f64, u8) = (500, 6.4, 1);
let first = x.0;
let second = x.1;
let third = x.2;
}
```
不带任何值的元组称为unit类型单元元组可以代表空值或者空的返回类型如下
```Rust
fn main() {
let x: () = (); // 将值()保存到类型为()的变量x中
}
```
### 3.2.2.2 数组
数组中的每个元素的类型必须相同,数组的长度是固定的,数组的定义方式如下:
```Rust
fn main() {
let a = [1, 2, 3, 4, 5]; //直接写数组的值
let b: [i32; 5] = [1, 2, 3, 4, 5]; //显示指定数组的类型和长度
let c: [i32; 5] = [3; 5]; //数组每个元素为同样的值等价于let a = [5, 5, 5, 5, 5];
}
```
数组通过索引来访问元素索引从0开始计数如下
```Rust
fn main() {
let a = [1, 2, 3, 4, 5];
let first = a[0]; // first = 1
let second = a[1]; // second = 2
}
```
Rust中访问无效的索引元素会报错如下
```Rust
fn main() {
let a = [1, 2, 3, 4, 5];
let b = a[5]; // 错误只能放为0-4所以这个代码将无法编译
}
```
## 3.2.3 类型转换 (少一个From, Into, TryFrom, TryInto)
Rust中可以使用as进行类型转换。
Rust中可以使用`as`进行类型转换。
- 数值类型之间默认不会隐式转换,如果要转换,则需手动使用`as`进行转换。
- bool类型可以转换为各种数值类型`false`对应`0``true`对应`1`
- 可以通过`as``char`类型转换为各种整型目标类型小于4字节时会从高位截断。
- 可以通过`as``u8`转换为`char`类型。
- 可以使用`std::char::from_u32``u32`转换为`char`类型。
- 可以使用`std::char::from_digit`将十进制整型转换为`char`类型。
- 数值类型之间默认不会隐式转换如果要转换则需手动使用as进行转换。
- bool类型可以转换为各种数值类型false对应0true对应1。
- 可以通过as将char类型转换为各种整型目标类型小于4字节时会从高位截断。
- 可以通过as将u8转换为char类型。
- 可以使用std::char::from_u32将u32转换为char类型。
- 可以使用std::char::from_digit将十进制整型转换为char类型。
```Rust
fn main() {
// 数值类型的转换

View File

@@ -2,375 +2,377 @@
## 3.20.1 使用线程
1. 相关概念
### 3.20.1.1 相关概念
- 进程是资源分配的最小单位线程是CPU调度的最小单位。
- 在使用多线程时,经常会遇到如下一些问题:
- 竞争状态:多个线程以不一致的顺序访问数据或资源;
- 死锁:两个线程相互等待对方停止使用其所拥有的资源,造成两者都永久等待;
- 只会发生在特定情况下且难以稳定重现和修复的bug。
- 编程语言提供的线程叫做绿色线程如go语言在底层实现了M:N的模型即M个绿色线程对应N个OS线程。但是Rust标准库只提供11的线程模型的实现即一个Rust线程对应一个Os线程。
- 进程是资源分配的最小单位线程是CPU调度的最小单位。
- 在使用多线程时,经常会遇到如下一些问题:
- 竞争状态:多个线程以不一致的顺序访问数据或资源;
- 死锁:两个线程相互等待对方停止使用其所拥有的资源,造成两者都永久等待;
- 只会发生在特定情况下且难以稳定重现和修复的bug。
- 编程语言提供的线程叫做绿色线程如go语言在底层实现了M:N的模型即M个绿色线程对应N个OS线程。但是Rust标准库只提供11的线程模型的实现即一个Rust线程对应一个Os线程。
2. 创建线程
### 3.20.1.2. 创建线程
创建一个新线程需要调用thread::spawn函数并传递一个闭包示例如下
创建一个新线程需要调用thread::spawn函数并传递一个闭包示例如下
```rust
use std::thread;
use std::time::Duration;
```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 1..5 {
println!("hi number {} from the main thread!", i);
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 1..5 {
println!("hi number {} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}
```
}
```
3. 等待线程结束
前面的例子并不能保证子线程执行完所有的打印因为主线程有可能在子线程执行完成前结束从而退出程序所以需要在主线程中等待子线程结束。等待子线程结束需要调用join方法示例如下
```rust
use std::thread;
use std::time::Duration;
### 3.20.1.3. 等待线程结束
fn main() {
let handle = thread::spawn(|| {
for i in 1..10 {
println!("hi number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});
前面的例子并不能保证子线程执行完所有的打印因为主线程有可能在子线程执行完成前结束从而退出程序所以需要在主线程中等待子线程结束。等待子线程结束需要调用join方法示例如下
for i in 1..5 {
println!("hi number {} from the main thread!", i);
```rust
use std::thread;
use std::time::Duration;
fn main() {
let handle = thread::spawn(|| {
for i in 1..10 {
println!("hi number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});
handle.join().unwrap(); // 等待子线程结束
for i in 1..5 {
println!("hi number {} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}
```
4. 线程与move闭包
handle.join().unwrap(); // 等待子线程结束
}
```
move关键字可用于传递给thread::spawn的闭包获取环境中的值的所有权从而达到将值的所有权从一个线程传送到另一个线程的目的。示例如下
```rust
use std::thread;
fn main() {
let v = vec![1, 2, 3];
let handle = thread::spawn(move || { // 将v移动进了闭包完成了值从主线程到子线程的过程
println!("Here's a vector: {:?}", v);
});
handle.join().unwrap();
}
```
### 3.20.1.4. 线程与move闭包
move关键字可用于传递给thread::spawn的闭包获取环境中的值的所有权从而达到将值的所有权从一个线程传送到另一个线程的目的。示例如下
```rust
use std::thread;
fn main() {
let v = vec![1, 2, 3];
let handle = thread::spawn(move || { // 将v移动进了闭包完成了值从主线程到子线程的过程
println!("Here's a vector: {:?}", v);
});
handle.join().unwrap();
}
```
## 3.20.2 传递消息
1. 通道简单介绍
### 3.20.2.1. 通道简单介绍
Rust中实现消息传递并发的主要工具是通道。通道由发送者和接受者两部分组成
- 发送者用来发送消息;
- 接收者用来接收消息;
- 发送者或者接收者任一被丢弃时就认为通道被关闭了。
Rust标准库中提供的通道叫做mpsc是多个生产者单个消费者的通道其使用示例如下
```rust
use std::sync::mpsc;
use std::thread;
Rust中实现消息传递并发的主要工具是通道。通道由发送者和接受者两部分组成
- 发送者用来发送消息;
- 接收者用来接收消息;
- 发送者或者接收者任一被丢弃时就认为通道被关闭了。
Rust标准库中提供的通道叫做mpsc是多个生产者单个消费者的通道其使用示例如下
```rust
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel(); //创建channel返回发送者、接收者
fn main() {
let (tx, rx) = mpsc::channel(); //创建channel返回发送者、接收者
thread::spawn(move || {
let val = String::from("hi");
tx.send(val).unwrap(); //使用发送者通过channel发送
});
thread::spawn(move || {
let val = String::from("hi");
tx.send(val).unwrap(); //使用发送者通过channel发送
});
let received = rx.recv().unwrap(); //使用接收者通过channel接收
println!("Got: {}", received);
}
```
let received = rx.recv().unwrap(); //使用接收者通过channel接收
println!("Got: {}", received);
}
```
关于mpsc通道的使用有以下几点说明
- 发送者的send方法返回一个Result类型如果接收端已经被丢弃了将没有发送值的目标所以发送操作将返回错误
- 接收者的recv方法也返回Result类型当通道发送端关闭时将返回一个错误值表明不会再由新的值到来了
- 接收还可以使用try_recv方法recv方法会阻塞到一直等待到消息到来而try_recv不会阻塞它会立即返回Ok值标识包含可用信息而Err则代表此时没有任何信息。
关于mpsc通道的使用有以下几点说明
- 发送者的send方法返回一个Result类型如果接收端已经被丢弃了将没有发送值的目标所以发送操作将返回错误
- 接收者的recv方法也返回Result类型当通道发送端关闭时将返回一个错误值表明不会再由新的值到来了
- 接收还可以使用try_recv方法recv方法会阻塞到一直等待到消息到来而try_recv不会阻塞它会立即返回Ok值标识包含可用信息而Err则代表此时没有任何信息。
2. 通道和所有权
### 3.20.2.2. 通道和所有权
在使用通道时send 函数会获取参数的所有权并移动这个值归接收者所有。例如下面的代码将会编译错误:
```rust
use std::sync::mpsc;
use std::thread;
在使用通道时send 函数会获取参数的所有权并移动这个值归接收者所有。例如下面的代码将会编译错误:
```rust
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let val = String::from("hi");
thread::spawn(move || {
let val = String::from("hi");
tx.send(val).unwrap();
println!("val is {}", val); //错误此处不能使用val因为val的所有权已经move到通道里面去了
});
let received = rx.recv().unwrap();
println!("Got: {}", received);
}
```
### 3.20.2.3. 发送多个值示例
利用通道发送多个值的示例如下:
```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();
println!("val is {}", val); //错误此处不能使用val因为val的所有权已经move到通道里面去了
});
thread::sleep(Duration::from_secs(1));
}
});
let received = rx.recv().unwrap();
for received in rx {
println!("Got: {}", received);
}
```
}
```
3. 发送多个值示例
### 3.20.2.4. 多个生产者示例
利用通道发送多个值的示例如下
```rust
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
mpsc是multiple producer, single consumer的缩写此通道可以有多个生产者。相对应的spmc通道则可以有单个生产者多个消费者。下面的示例演示多个生产者、单个消费者一起工作
fn main() {
let (tx, rx) = mpsc::channel();
```rust
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
thread::spawn(move || {
let vals = vec![
String::from("hi"),
String::from("from"),
String::from("the"),
String::from("thread"),
];
fn main() {
let (tx, rx) = mpsc::channel();
let tx1 = mpsc::Sender::clone(&tx); //通过clone来使用
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);
for val in vals {
tx1.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
}
```
});
4. 多个生产者示例
thread::spawn(move || {
//第二个发送者线程
let vals = vec![
String::from("hi"),
String::from("from"),
String::from("the"),
String::from("thread"),
];
mpsc是multiple producer, single consumer的缩写此通道可以有多个生产者。相对应的spmc通道则可以有单个生产者多个消费者。下面的示例演示多个生产者、单个消费者一起工作
```rust
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
let (tx, rx) = mpsc::channel();
let tx1 = mpsc::Sender::clone(&tx); //通过clone来使用
thread::spawn(move || {
//第一个发送者线程
let vals = vec![
String::from("hi"),
String::from("from"),
String::from("the"),
String::from("thread"),
];
for val in vals {
tx1.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
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);
for val in vals {
tx.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
}
```
});
上面代码中,有两个发送者发送数据,一个接收者结束数据。
for received in rx {
println!("Got: {}", received);
}
}
```
上面代码中,有两个发送者发送数据,一个接收者结束数据。
## 3.20.3 共享内存
某些情况下多个线程之间需要共享内存即多个线程都访问某个变量。如何在多个线程间安全的共享内存就需要用到锁、原子操作等机制下面主要介绍Rust中锁的使用。
1. 互斥锁Mutex
### 3.20.3.1. 互斥锁Mutex
在任意时刻互斥锁只允许一个线程访问数据在使用数据前需获取锁使用完成后需要释放锁。关于Mutex的用法示例如下
```rust
use std::sync::Mutex;
fn main() {
let m = Mutex::new(5);
{
let mut num = m.lock().unwrap();
*num = 6; // 可以对Mutex内部的值进行修改可见Mutex和RefCell类似提供内部可变性
} // 离开作用域Mutex<T>的锁会自动释放
println!("m = {:?}", m);
在任意时刻互斥锁只允许一个线程访问数据在使用数据前需获取锁使用完成后需要释放锁。关于Mutex的用法示例如下
```rust
use std::sync::Mutex;
fn main() {
let m = Mutex::new(5);
{
let mut num = m.lock().unwrap();
*num = 6; // 可以对Mutex内部的值进行修改可见Mutex和RefCell类似提供内部可变性
} // 离开作用域Mutex<T>的锁会自动释放
println!("m = {:?}", m);
}
```
Mutex本质上是一个智能指针lock()返回一个叫做MutexGuard的智能指针其内部提供了drop方法所以当MutexGuard离开作用域时会自动释放锁。Mutex获取到锁后可以对其内部的值进行修改可见它和RefCell类似也提供了内部可变性。
### 3.20.3.2. 多线程与多所有权
有了Mutex之后能对共享数据进行保护但是由于Rust的所有权机制每个值都有且仅有一个所有者。下面的代码将无法通过编译
```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 || { // 不能将counter移到多个线程中
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
```
Mutex本质上是一个智能指针lock()返回一个叫做MutexGuard的智能指针其内部提供了drop方法所以当MutexGuard离开作用域时会自动释放锁。Mutex获取到锁后可以对其内部的值进行修改可见它和RefCell类似也提供了内部可变性。
2. 多线程与多所有权
有了Mutex之后能对共享数据进行保护但是由于Rust的所有权机制每个值都有且仅有一个所有者。下面的代码将无法通过编译
```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 || { // 不能将counter移到多个线程中
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
for handle in handles {
handle.join().unwrap();
}
```
println!("Result: {}", *counter.lock().unwrap());
}
```
因此要在多个线程间共享内存还需要有一种机制能让多个线程都能访问Mutex保护的数据。根据本书目前学到的知识可以想到Rc智能指针。上面的代码使用Rc智能指针后的示例如下
```rust
use std::rc::Rc;
use std::sync::Mutex;
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());
因此要在多个线程间共享内存还需要有一种机制能让多个线程都能访问Mutex保护的数据。根据本书目前学到的知识可以想到Rc智能指针。上面的代码使用Rc智能指针后的示例如下
```rust
use std::rc::Rc;
use std::sync::Mutex;
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);
}
```
上面的代码仍将编译错误因为Rc是非线程安全的此时需要使用Arc类型。Arc是一个类似于Rc并可以安全的用于并发环境的类型使用Arc的示例如下
```rust
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0)); // 使用Arc共享
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());
for handle in handles {
handle.join().unwrap();
}
```
println!("Result: {}", *counter.lock().unwrap());
}
```
3. 读写锁
上面的代码仍将编译错误因为Rc是非线程安全的此时需要使用Arc类型。Arc是一个类似于Rc并可以安全的用于并发环境的类型使用Arc的示例如下
读变量并不会改变变量的状态互斥锁Mutex每次读写都会加锁当在有大量读、少量写的场景时使用Mutex就会效率不高此时可以使用读写锁RwLock示例如下
```rust
use std::sync::{Arc, Mutex};
use std::thread;
```rust
use std::sync::{Arc, RwLock};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0)); // 使用Arc共享
let mut handles = vec![];
fn main() {
let shared_data = Arc::new(RwLock::new(vec![1, 2, 3])); // 创建一个共享的 vector 数据
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
let mut threads = vec![];
// 创建 5 个读线程
for i in 0..5 {
let shared_data = shared_data.clone();
threads.push(thread::spawn(move || {
// 获取读锁
let shared_data = shared_data.read().unwrap();
println!("Thread {} read data: {:?}", i, *shared_data);
}));
}
// 创建 2 个写线程
for i in 0..2 {
let shared_data = shared_data.clone();
threads.push(thread::spawn(move || {
// 获取写锁
let mut shared_data = shared_data.write().unwrap();
println!("Thread {} write data", i);
shared_data.push(i); // 向 vector 中添加数据
}));
}
// 等待所有线程完成
for thread in threads {
thread.join().unwrap();
}
*num += 1;
});
handles.push(handle);
}
```
4. 小结
for handle in handles {
handle.join().unwrap();
}
关于Mutex和RwLock的选择
println!("Result: {}", *counter.lock().unwrap());
}
```
- 追求高并发读取时使用RwLock因为Mutex一次只允许一个线程读取
- 如果要保证写操作的成功性使用Mutex
- 不知道哪个合适统一使用Mutex。
### 3.20.3.3. 读写锁
关于Mutex和Arc类型
读变量并不会改变变量的状态互斥锁Mutex每次读写都会加锁当在有大量读、少量写的场景时使用Mutex就会效率不高此时可以使用读写锁RwLock示例如下
- Mutex和RefCell一样具有内部可变性不过Mutex是线程安全的而RefCell不是线程安全的
- Arc和Rc功能类似但是Arc是线程安全的而Rc不是线程安全的。
```rust
use std::sync::{Arc, RwLock};
use std::thread;
fn main() {
let shared_data = Arc::new(RwLock::new(vec![1, 2, 3])); // 创建一个共享的 vector 数据
let mut threads = vec![];
// 创建 5 个读线程
for i in 0..5 {
let shared_data = shared_data.clone();
threads.push(thread::spawn(move || {
// 获取读锁
let shared_data = shared_data.read().unwrap();
println!("Thread {} read data: {:?}", i, *shared_data);
}));
}
// 创建 2 个写线程
for i in 0..2 {
let shared_data = shared_data.clone();
threads.push(thread::spawn(move || {
// 获取写锁
let mut shared_data = shared_data.write().unwrap();
println!("Thread {} write data", i);
shared_data.push(i); // 向 vector 中添加数据
}));
}
// 等待所有线程完成
for thread in threads {
thread.join().unwrap();
}
}
```
### 3.20.3.4. 小结
关于Mutex和RwLock的选择
- 追求高并发读取时使用RwLock因为Mutex一次只允许一个线程读取
- 如果要保证写操作的成功性使用Mutex
- 不知道哪个合适统一使用Mutex。
关于Mutex和Arc类型
- Mutex和RefCell一样具有内部可变性不过Mutex是线程安全的而RefCell不是线程安全的
- Arc和Rc功能类似但是Arc是线程安全的而Rc不是线程安全的。
## 3.20.4 Send trait和Sync trait
1. Send和Sync
### 3.20.4.1. Send和Sync
Send trait和Sync trait是Rust语言中的两个标记trait即未定义任何行为但是可以标记一个类型其作用如下
- 实现了Send的类型可以在线程间安全的传递其所有权
- 实现了Sync的类型可以在线程间通过引用安全的共享。
Send trait和Sync trait是Rust语言中的两个标记trait即未定义任何行为但是可以标记一个类型其作用如下
- 实现了Send的类型可以在线程间安全的传递其所有权
- 实现了Sync的类型可以在线程间通过引用安全的共享。
实现Sync的类型是通过引用在线程间共享的因此一个类型要在线程间安全的共享那么它的应用必须能安全的在线程间进行传递。所以可以有结论若&T满足Send那么T满足Sync。
实现Sync的类型是通过引用在线程间共享的因此一个类型要在线程间安全的共享那么它的应用必须能安全的在线程间进行传递。所以可以有结论若&T满足Send那么T满足Sync。
2. 实现了Send和Sync的类型
### 3.20.4.2. 实现了Send和Sync的类型
Rust中几乎所有类型都默认实现了Send和Sync。Send和Sync也是可自动派生的trait因此一个复合类型如果其内部成员都实现了Send或Sync那么它就自动实现了Send或Sync。
Rust中几乎所有类型都默认实现了Send和Sync。Send和Sync也是可自动派生的trait因此一个复合类型如果其内部成员都实现了Send或Sync那么它就自动实现了Send或Sync。
Rust中绝大多数类型都实现了Send和Sync但是下面几个是没有实现Send或者Sync的:
Rust中绝大多数类型都实现了Send和Sync但是下面几个是没有实现Send或者Sync的:
- 裸指针既没有实现Send也没有实现Sync因为它本身就没有任何安全保证的
- UnsafeCell没有实现SyncCell和RefCell也没有实现Sync但是它们实现了Send
- Rc既没有实现Send也没有实现Sync。
- 裸指针既没有实现Send也没有实现Sync因为它本身就没有任何安全保证的
- UnsafeCell没有实现SyncCell和RefCell也没有实现Sync但是它们实现了Send
- Rc既没有实现Send也没有实现Sync。
通常情况下不需要为某个类型手动实现Send和Sync trait手动实现这些标记trait 涉及到编写不安全的Rust代码本书在后面unsafe编程部分介绍。

View File

@@ -4,136 +4,136 @@
到此节之前本书介绍的基本都是安全的Rust即Rust编译时会强制执行内存安全保证的检查只要编译通过基本就能保证代码的安全性。既然有安全的Rust那么必然也有不安全的Rust。
1. 不安全的Rust存在的原因
### 3.21.1.1 不安全的Rust存在的原因
- 代码静态分析相对保守,这就意味着某些代码可能是合法的,但是编译器检查也会拒绝通过。在此情况下,可以使用不安全的代码。
- 底层计算机硬件固有的不安全性。如果Rust不允许进行不安全的操作有些任务根本就完成不了。
- 代码静态分析相对保守,这就意味着某些代码可能是合法的,但是编译器检查也会拒绝通过。在此情况下,可以使用不安全的代码。
- 底层计算机硬件固有的不安全性。如果Rust不允许进行不安全的操作有些任务根本就完成不了。
2. 不安全的Rust使用的场景
### 3.21.1.2. 不安全的Rust使用的场景
Rust通过unsafe关键字切换到不安全的Rust不安全的Rust使用的场景如下
- 解引用裸指针;
- 调用不安全的函数或者方法;
- 访问或修改可变静态变量;
- 实现不安全的trait。
Rust通过unsafe关键字切换到不安全的Rust不安全的Rust使用的场景如下
- 解引用裸指针;
- 调用不安全的函数或者方法;
- 访问或修改可变静态变量;
- 实现不安全的trait。
注意unsafe并不会关闭借用检查器或禁用任何其它的Rust安全检查规则它只提供上述几个不被编译器检查内存安全的功能。unsafe也不意味着块中的代码一定是有问题的它只是表示由程序员来确保安全。
## 3.21.2 使用unsafe编程
1. 解引用裸指针
### 3.21.2.1. 解引用裸指针
裸指针又称为原生指针在功能上和引用类似但是引用的安全由Rust编译器保证裸指针则不是。裸指针需要显式标明可变性不可变的裸指针分别写作 ``*const T`` ,可变的裸指针写作 ``*mut T``。
裸指针又称为原生指针在功能上和引用类似但是引用的安全由Rust编译器保证裸指针则不是。裸指针需要显式标明可变性不可变的裸指针分别写作 ``*const T`` ,可变的裸指针写作 ``*mut T``。
裸指针与引用和只能指针的区别如下:
- 可以忽略借用规则,在代码中同时拥有不可变和可变的裸指针,或多个指向相同位置的可变裸指针;
- 不保证裸指针指向有效的内存;
- 允许裸指针为空;
- 裸指针不能实现任何自动清理功能。
裸指针与引用和只能指针的区别如下:
- 可以忽略借用规则,在代码中同时拥有不可变和可变的裸指针,或多个指向相同位置的可变裸指针;
- 不保证裸指针指向有效的内存;
- 允许裸指针为空;
- 裸指针不能实现任何自动清理功能。
可以在安全代码中创建可变或不可变的裸指针,但是只能在不安全块中解引用裸指针,示例如下:
可以在安全代码中创建可变或不可变的裸指针,但是只能在不安全块中解引用裸指针,示例如下:
```rust
fn main() {
let mut num = 5;
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;
let address = 0x012345usize;
let _r = address as *const i32;
unsafe {
println!("r1 is: {}", *r1);
println!("r2 is: {}", *r2);
}
```rust
fn main() {
let mut num = 5;
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;
let address = 0x012345usize;
let _r = address as *const i32;
unsafe {
println!("r1 is: {}", *r1);
println!("r2 is: {}", *r2);
}
```
}
```
说明创建一个裸指针不会造成任何危险只有当访问其指向的值时并且这个值已经无效才可能造成危险所以可以在安全的代码块中创建裸指针但是使用则必须在unsafe的块中。
说明创建一个裸指针不会造成任何危险只有当访问其指向的值时并且这个值已经无效才可能造成危险所以可以在安全的代码块中创建裸指针但是使用则必须在unsafe的块中。
2. 调用不安全的函数或者方法
### 3.21.2.2. 调用不安全的函数或者方法
示例1代码如下
示例1代码如下
```rust
unsafe fn dangerous() {
println!("Do some dangerous thing");
```rust
unsafe fn dangerous() {
println!("Do some dangerous thing");
}
fn main() {
unsafe {
dangerous();
}
fn main() {
unsafe {
dangerous();
}
println!("Hello, world!");
println!("Hello, world!");
}
```
示例2代码如下
```rust
fn foo() {
let mut num = 5;
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;
unsafe {
println!("r1 is: {}", *r1);
println!("r2 is: {}", *r2);
}
```
}
fn main() {
foo();
}
```
示例2代码如下
```rust
fn foo() {
let mut num = 5;
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;
unsafe {
println!("r1 is: {}", *r1);
println!("r2 is: {}", *r2);
}
### 3.21.2.3. 访问或者修改可变静态变量
全局变量在Rust中被称为静态变量。静态变量的名称采用SCREAMING_SNAKE_CASE写法它只能存储拥有 'static生命周期的引用。下面的示例使用了不可变的静态变量
```rust
static HELLO_WORLD: &str = "Hello, world!"; // 不可变的静态变量
fn main() {
println!("name is: {}", HELLO_WORLD);
}
```
这里对比一下常量和静态变量:
- 静态变量中的值有一个固定的内存地址(即使用这个值总会访问相同的地址),常量则允许在任何被用到的时候复制其数据。
- 静态变量可以是可变的,虽然这可能是不安全的。
下面的示例使用可变的静态变量:
```rust
static mut COUNTER: u32 = 0; // 可变的静态变量同样需要mut关键字
fn add_to_count(inc: u32) {
unsafe {
COUNTER += inc;
}
fn main() {
foo();
}
fn main() {
add_to_count(3);
unsafe {
println!("COUNTER: {}", COUNTER);
}
```
}
```
3. 访问或者修改可变静态变量
### 3.21.2.4. 实现不安全的trait
全局变量在Rust中被称为静态变量。静态变量的名称采用SCREAMING_SNAKE_CASE写法它只能存储拥有 'static生命周期的引用。下面的示例使用了不可变的静态变量
当至少有一个方法中包含编译器不能验证的invariant时该trait就是不安全的。在trait之前增加unsafe关键字将trait声明为unsafe同时trait的实现也必须标记为unsafe。下面为使用unsafe的trait的示例
```rust
static HELLO_WORLD: &str = "Hello, world!"; // 不可变的静态变量
fn main() {
println!("name is: {}", HELLO_WORLD);
```rust
struct Bar;
unsafe trait Foo {
fn foo(&self);
}
unsafe impl Foo for Bar {
fn foo(&self) {
println!("foo");
}
```
}
这里对比一下常量和静态变量:
- 静态变量中的值有一个固定的内存地址(即使用这个值总会访问相同的地址),常量则允许在任何被用到的时候复制其数据。
- 静态变量可以是可变的,虽然这可能是不安全的。
下面的示例使用可变的静态变量:
```rust
static mut COUNTER: u32 = 0; // 可变的静态变量同样需要mut关键字
fn add_to_count(inc: u32) {
unsafe {
COUNTER += inc;
}
}
fn main() {
add_to_count(3);
unsafe {
println!("COUNTER: {}", COUNTER);
}
}
```
4. 实现不安全的trait
当至少有一个方法中包含编译器不能验证的invariant时该trait就是不安全的。在trait之前增加unsafe关键字将trait声明为unsafe同时trait的实现也必须标记为unsafe。下面为使用unsafe的trait的示例
```rust
struct Bar;
unsafe trait Foo {
fn foo(&self);
}
unsafe impl Foo for Bar {
fn foo(&self) {
println!("foo");
}
}
fn main() {
let a = Bar;
a.foo();
}
```
fn main() {
let a = Bar;
a.foo();
}
```

View File

@@ -3,6 +3,7 @@
## 3.3.1. 函数定义
fn关键字、函数名、函数参数名及其类型如果有的话、返回值类型如果有的话组成函数签名 加上由一对花括号包含的函数体组成函数。例子如下:
```Rust
// 一个没有参数,也没有返回值的函数
fn print_line() {
@@ -30,6 +31,7 @@ fn main() {
```
Rust中函数也可以定义在函数内部如下
```Rust
fn calculate(a: u32, b: u32) {
println!("a is {:?}", a);
@@ -54,6 +56,7 @@ fn main() {
## 3.3.2. 语句和表达式
Rust中语句是执行一个写操作但不返回值的指令表达式则计算并产生一个值。
```Rust
fn main() {
let a = 1u32; // "1u32"就是一个表达式, “let a = 1u32;”则是一个语句
@@ -70,41 +73,43 @@ fn main() {
## 3.3.3. 函数返回值
- 使用return指定返回值如下
```Rust
fn sum(a: u32, b: u32) -> u32 {
let r = a + b;
return r //可以加分号,也可以不加分号, 所以这行等价于“return r;”
}
fn main() {
let a = 1u32;
let b = 2u32;
let c = sum(a, b);
println!("c = {:?}", c);
}
```
```Rust
fn sum(a: u32, b: u32) -> u32 {
let r = a + b;
return r //可以加分号,也可以不加分号, 所以这行等价于“return r;”
}
特别的return关键字不指定值时表示返回的是(),如下:
```Rust
fn my_function() -> () {
println!("some thing");
return; //等价于 “return ()”
}
```
fn main() {
let a = 1u32;
let b = 2u32;
let c = sum(a, b);
println!("c = {:?}", c);
}
```
特别的return关键字不指定值时表示返回的是(),如下:
```Rust
fn my_function() -> () {
println!("some thing");
return; //等价于 “return ()”
}
```
- 不使用return关键字将返回最后一条执行的表达式的计算结果如下
```Rust
fn sum(a: u32, b: u32) -> u32 {
println!("a is {:?}", a);
println!("b is {:?}", b);
a + b //注意,是没有加分号的
}
```Rust
fn sum(a: u32, b: u32) -> u32 {
println!("a is {:?}", a);
println!("b is {:?}", b);
fn main() {
let a = 1u32;
let b = 2u32;
let c = sum(a, b);
println!("c = {:?}", c);
}
```
a + b //注意,是没有加分号的
}
fn main() {
let a = 1u32;
let b = 2u32;
let c = sum(a, b);
println!("c = {:?}", c);
}
```

View File

@@ -17,6 +17,7 @@
2块注释使用“/*... */”。
示例如下:
```Rust
/*
* 块注释:
@@ -46,6 +47,7 @@ Rust提供了cargo doc命令可以把文档注释转换成html网页最终展
2文档块注释使用“/**...*/”。
示例如下:
```Rust
// 下面是文档行注释
@@ -90,8 +92,11 @@ fn main() {
```
运行如下命令:
```
```bash
cargo doc --open
```
将打开上面代码里面文档注释生成的文档,如下图:
![注释](../assets/1.png)

View File

@@ -2,163 +2,173 @@
Rust中的控制流结构主要包括
- if条件判断;
- loop循环
- while循环
- for .. in 循环。
- `if`条件判断;
- `loop`循环;
- `while`循环;
- `for .. in` 循环。
## 3.5.1. if条件判断
- if执行条件判断,示例如下:
```Rust
fn main() {
let a = 2u32;
- `if`执行条件判断,示例如下:
if a > 5u32 {
println!("a > 5");
} else {
println!("a <= 5");
```Rust
fn main() {
let a = 2u32;
if a > 5u32 {
println!("a > 5");
} else {
println!("a <= 5");
}
}
}
```
```
- if - else if处理多重条件示例如下
```Rust
fn main() {
let a = 3u32;
- `if - else if`处理多重条件,示例如下:
if a > 5u32 {
println!("a > 5");
} else if a > 4u32 {
println!("a > 4");
} else if a > 3u32 {
println!("a > 3");
} else if a > 2u32 {
println!("a > 2");
} else if a > 1u32 {
println!("a > 1");
} else {
println!("a = 1");
```Rust
fn main() {
let a = 3u32;
if a > 5u32 {
println!("a > 5");
} else if a > 4u32 {
println!("a > 4");
} else if a > 3u32 {
println!("a > 3");
} else if a > 2u32 {
println!("a > 2");
} else if a > 1u32 {
println!("a > 1");
} else {
println!("a = 1");
}
}
}
```
```
-let语句中使用if
- 在`let`语句中使用`if`
if是一个表达式所以可以在let右侧使用如下
```Rust
fn main() {
let a = 3u32;
`if`是一个表达式,所以可以在`let`右侧使用,如下:
let a_bigger_than_two: bool = if a > 2u32 {
true
} else {
false
};
```Rust
fn main() {
let a = 3u32;
if a_bigger_than_two {
println!("a > 2");
} else {
println!("a <= 2");
let a_bigger_than_two: bool = if a > 2u32 {
true
} else {
false
};
if a_bigger_than_two {
println!("a > 2");
} else {
println!("a <= 2");
}
}
}
```
```
## 3.5.2. loop循环
- loop重复执行代码
```Rust
fn main() {
// 一直循环打印 again
loop {
println!("again!");
}
}
```
- 使用break终止循环
```Rust
fn main() {
let mut counter = 0;
loop {
println!("counter = {:?}", counter);
counter += 1;
if counter == 10 {
break; // 将终止循环
```Rust
fn main() {
// 一直循环打印 again
loop {
println!("again!");
}
}
}
```
上面的代码将打印10次遇到break后终止循环。另外break也可以返回值如下
```Rust
fn main() {
let mut counter = 0;
```
let result = loop {
counter += 1;
- 使用`break`终止循环
if counter == 10 {
break counter * 2;
```Rust
fn main() {
let mut counter = 0;
loop {
println!("counter = {:?}", counter);
counter += 1;
if counter == 10 {
break; // 将终止循环
}
}
};
println!("The result is {result}");
}
```
- 使用continue可以直接跳到下一轮循环
```Rust
fn main() {
let mut x = 0;
// 此循环将只打印10以内的奇数
loop {
x += 1;
if x == 10 {
break;
}
if x % 2 == 0 {
continue; //将直接跳到下一轮循环
}
println!("{}", x);
}
}
```
```
上面的代码将打印10次遇到`break`后终止循环。另外,`break`也可以返回值,如下:
```Rust
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("The result is {result}");
}
```
- 使用`continue`可以直接跳到下一轮循环
```Rust
fn main() {
let mut x = 0;
// 此循环将只打印10以内的奇数
loop {
x += 1;
if x == 10 {
break;
}
if x % 2 == 0 {
continue; //将直接跳到下一轮循环
}
println!("{}", x);
}
}
```
## 3.5.3. while条件循环
- while条件循环执行代码当条件不满足后结束循环如下
```Rust
fn main() {
let mut cnt = 0u32;
while cnt < 10 {
println!("cnt = {:?}", cnt);
cnt += 1;
}
}
```
- 在while循环中也可以使用break和continue,如下:
```Rust
fn main() {
let mut x = 0;
- `while`条件循环执行代码,当条件不满足后结束循环,如下:
while x < 10 {
x += 1;
println!("{}", x);
if x % 2 == 0 {
continue; // 跳到下一轮循环
}
if x > 8 {
break; // 提前结束
```Rust
fn main() {
let mut cnt = 0u32;
while cnt < 10 {
println!("cnt = {:?}", cnt);
cnt += 1;
}
}
}
```
```
## 3.5.4. for .. in 循环
- 在`while`循环中也可以使用`break`和`continue`,如下:
```Rust
fn main() {
let mut x = 0;
while x < 10 {
x += 1;
println!("{}", x);
if x % 2 == 0 {
continue; // 跳到下一轮循环
}
if x > 8 {
break; // 提前结束
}
}
}
```
## 3.5.4. `for .. in` 循环
`for`循环用来对一个集合的每个元素执行一些代码,使用方式如下:
for循环用来对一个集合的每个元素执行一些代码使用方式如下
```Rust
fn main() {
let a = [10, 20, 30, 40, 50];

View File

@@ -41,18 +41,27 @@ fn main() {
let _r = f2(a, b);
}
```
对于上面的代码在执行第17行和第18行的栈帧示意图如下
![注释](../../assets/3.png)
这里需要注意的是两个帧对应同样的内存地址这是因为在调用完f1函数后其对应的栈帧释放释放的实际意义就是这段内存可以被重新分配了然后调用f2函数为其分配栈帧时从同样的地址进行分配。
### 2. 堆
堆空间和栈空间不同,不由操作系统管理,在需要时申请,不需要时释放。申请和释放堆内存是一件困难的事情,尤其当程序代码较多时。只申请堆内存而不释放会造成内存泄露,内存泄露过多会造成内存耗尽而崩溃。错误的释放在使用的内存也会造成程序运行错误(或直接无法运行)。
有些编程语言使用提供垃圾管理回收器GC来自动回收不再使用的堆内存有些语言必须完全由程序员在代码中手动申请和释放内存。
Rust没有GC但通过其独特的机制管理内存程序员不用手动申请和释放堆内存。
### 3. Rust如何使用堆和栈
栈中存储的所有数据都必须占用(在编译时就)已知且固定的大小。编译时大小未知或可能变化的数据,存储在堆上。
数据存放到栈上时,是直接将数据放到栈内存。
当数据需要存放到堆上时,内存分配器则是根据数据的大小,在堆内存找到合适大小的空区域存放,把它标记为已使用,并返回一个表示该位置地址的指针。该指针存储在栈上,当需要访问具体的数据时,必须先访问指针,然后通过指针找到堆上的位置,从而访问数据。这个过程可以用下图表示:
![注释](../../assets/4.png)

View File

@@ -1,4 +1,5 @@
# 3.7.1 所有权介绍
所有权是Rust最为与众不同的特性它让Rust无需垃圾回收即可保证内存安全。
## 1. 所有权规则
@@ -8,6 +9,7 @@ Rust所有权的规则如下
- Rust中的每个值都有一个被称为其所有者的变量即值的所有者是某个变量
- 值在任何时刻有且仅有一个所有者;
- 当所有者离开作用域后,这个值将丢弃。
```Rust
fn main() {
let a: u32 = 8;
@@ -21,7 +23,9 @@ fn main() {
注意b是`String::from("hello")`的所有者但是b不是字符串`"hello"`的所有者。同理c是`vec![1, 2, 3]`的所有者,但不是`[1, 2, 3]`的所有者。至于为什么后续内容String类型部分会进行讲解。
## 2. 变量的作用域
变量作用域是变量在程序中有效的范围。一对花括号表示的范围就是作用域,变量有效的范围就是从创建开始,到离开作用域结束。
示例1
```Rust
@@ -62,11 +66,11 @@ fn main() {
## 3. String类型
- String类型的创建有下面三种方式
### 3.1 String类型的创建有下面三种方式
- `String::from`
- `to_string`
- `String::new`
- `String::from`
- `to_string`
- `String::new`
```Rust
fn main() {
@@ -87,7 +91,7 @@ fn main() {
}
```
- String类型的本质
### 3.2 String类型的本质
Rust标准库中String类型的定义如下
@@ -138,7 +142,7 @@ struct String {
所以String类型本质是三个字段一个指针一个容量大小一个长度大小。
- 内存分配
### 3.3 内存分配
在Rust中编译时大小确定的数据放在栈上编译时大小不能确定的数据放在堆上。考虑如下代码
@@ -162,7 +166,7 @@ String类型本身是三个字段指针、长度、容量在编译时
Rust所有权规则第二条在任意时刻值有且仅有一个所有者。那么当一个变量赋给另外一个变量时发生了什么
- 完全存储在栈上的类型
### 4.1 完全存储在栈上的类型
考虑如下代码:
```Rust
@@ -175,7 +179,7 @@ fn main() {
x和y都是u32类型在编译时知道大小都存储在栈上。代码第2行是将5绑定到变量`x`第3行则是通过自动拷贝的方式将5绑定到`y`上(先拷贝`x`的值5然后将拷贝后得到的5绑定到y上。所以`let y = x`发生后这段代码里面最后有两个值5分别绑定到了`x``y`上。
- 涉及到堆存储的类型
### 4.2 涉及到堆存储的类型
再考虑如下代码:
@@ -198,14 +202,15 @@ s是`String`类型,字符串`"Hello world"`是存储在堆内存上的,其
`let s1 = s`执行后就发生了所有权的转移String类型值的所有权从`s`转移到了`s1`。此时Rust认为原来的`s`不再有效。因此上面代码第4行打开编译将会出错。
## 5. 浅拷贝与深拷贝
- 浅拷贝
### 5.1 浅拷贝
只拷贝栈上的内容,就叫做浅拷贝。
对于上面的String类型执行`let s1 = s`后,只把`s``ptr``len``cap`中的值拷贝给`s1``ptr``len``cap`的值,这种就叫做浅拷贝。浅拷贝发生后,`s``ptr``s1``ptr`都指向同样的堆内存。内存布局如下:
![注释](../../assets/8.png)
- 深拷贝
### 5.2 深拷贝
除了拷贝栈上的内容外,还拷贝堆内存中的内容,就叫做深拷贝。
对于上面的String类型执行`let s1 = s`后,除了把`s``len``cap`中的值拷贝给`s1``len``cap`还在堆上重新分配一块内存将s的ptr指向的堆内存的内容拷贝到这块内存然后`s1``ptr`指向这块内存,这种拷贝就叫做深拷贝。深拷贝发生后,`s``ptr``s1``ptr`指向不同的堆内存,但是堆内存中存储的内容一样。深拷贝发生后的内存布局如下:
@@ -241,7 +246,8 @@ Rust中默认实现了`Copy trait`的类型有:
- 共享指针类型或共享引用类型。
## 8. 所有权和函数
- 将值传给函数
### 8.1 将值传给函数
在将值传递给函数时,和变量赋值一样会发生值的移动(或复制),如下:
```Rust
@@ -264,9 +270,10 @@ fn makes_copy(some_integer: i32) {
}
```
- 返回值和作用域
### 8.2 返回值和作用域
函数的返回值也可以转移所有权,如下:
```Rust
fn main() {
let s1 = gives_ownership(); // gives_ownership 将返回值转移给 s1

View File

@@ -100,7 +100,7 @@ fn change(some_string: &String) {
## 2. 可变引用
- 使用可变引用
### 2.1 使用可变引用
可以通过可变引用改变变量的值,对一个变量加上`&mut`就是对其的可变引用,示例如下:
```Rust
@@ -114,7 +114,7 @@ fn change(some_string: &mut String) {
}
```
- 引用的作用域
### 2.2 引用的作用域
前文讲过变量的作用域是从定义开始到花括号结束的位置,如:
@@ -149,7 +149,8 @@ fn change(some_string: &mut String) {
}
```
- 使用可变引用的限制
### 2.3 使用可变引用的限制
1限制一**同一作用域,特定数据只能有一个可变引用**。如下代码会报错:
```Rust
@@ -235,7 +236,8 @@ fn main() {
数据竞争会导致未定义行为难以在运行时追踪并且难以诊断和修复Rust 避免了此情况的发生,因为它甚至不会编译存在数据竞争的代码!
## 3. 悬垂引用
- 什么是悬垂引用(悬垂指针)?
### 3.1 什么是悬垂引用(悬垂指针)?
在具有指针的语言中如C/C++很容易通过释放内存但是保留指向它的指针而错误的生成一个悬垂指针。例如有如下C代码
@@ -271,7 +273,7 @@ int main()
第14行执行后`ptr`就变成了一个悬垂指针或者交悬垂引用然后在第16行继续使用`ptr`,则会发生错误。
- 在 Rust 中,编译器确保引用永远不会变成悬垂状态。
### 3.2 在 Rust 中,编译器确保引用永远不会变成悬垂状态。
如下代码因为会产生悬垂引用,编译将不会通过:
@@ -285,18 +287,18 @@ fn dangle() -> &String {
}
```
*思考:为什么下面的代码是正确的*
```Rust
fn main() {
let s = no_dangle();
println!("s = {:?}", s);
}
fn no_dangle() -> String {
let s = String::from("hello");
s
} // 此处s虽然离开了函数这个作用域范围但是它的所有权是被转移出去了值并没有释放
```
> *思考:为什么下面的代码是正确的*
> ```Rust
> fn main() {
> let s = no_dangle();
> println!("s = {:?}", s);
>}
>
>fn no_dangle() -> String {
> let s = String::from("hello");
> s
>} // 此处s虽然离开了函数这个作用域范围但是它的所有权是被转移出去了值并没有释放
>```
## 4. 引用的规则总结

View File

@@ -12,146 +12,160 @@ Rust中可以通过结构体或者枚举来构造复杂的数据类型结构
### 1. 定义和实例化
- 元组结构体的定义和实例化
#### 1.1 元组结构体的定义和实例化
下面是定义一个元组结构体,用来表示颜色。
```rust
struct Color(i32, i32, i32);
下面是定义一个元组结构体,用来表示颜色。
// 实例化元组结构体
let color = Color(1, 1, 1);
```
```rust
struct Color(i32, i32, i32);
使用元组结构体的特点是,给定元组具体的名字,可以和同类元组内的类型的元组做区分。
// 实例化元组结构体
let color = Color(1, 1, 1);
```
```rust
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
使用元组结构体的特点是,给定元组具体的名字,可以和同类元组内的类型的元组做区分。
// 实例化元组结构体
let color = Color(1, 1, 1);
let point = Point(1, 1, 1);
```
可以看到Color 和Point虽然元组内的元素都是i32但是给定了(i32, i32,i32)这个元组两个不同的结构体名称所以Color和Point不是同一种类型。
```rust
#[derive(Debug, Eq, PartialEq)]
pub struct Color(i32, i32, i32);
```rust
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
#[derive(Debug, Eq, PartialEq)]
pub struct Point(i32, i32, i32);
// 实例化元组结构体
let color = Color(1, 1, 1);
let point = Point(1, 1, 1);
```
fn main() {
assert_eq!(Color(1, 1, 1), Point(1, 1, 1)); // error 判断两个元组结构体是不是相等rust将不同类型名称的结构体看作是不一样的结构体虽然结构内的元素都是一样的。
}
```
可以看到Color 和Point虽然元组内的元素都是i32但是给定了(i32, i32,i32)这个元组两个不同的结构体名称所以Color和Point不是同一种类型。
- 经典的C结构体的定义和初始化
```rust
#[derive(Debug, Eq, PartialEq)]
pub struct Color(i32, i32, i32);
结构体内的每个字段都是命名字段,使得访问和修改更直观。
```rust
// 定义结构体
struct Person {
name: String,
age: u32,
height: f32,
}
#[derive(Debug, Eq, PartialEq)]
pub struct Point(i32, i32, i32);
// 结构体实例化
let alice = Person {
name: String::from("Alice"),
age: 30,
height: 1.65,
};
```
fn main() {
assert_eq!(Color(1, 1, 1), Point(1, 1, 1)); // error 判断两个元组结构体是不是相等rust将不同类型名称的结构体看作是不一样的结构体虽然结构内的元素都是一样的。
}
```
思考结构体内的数据可以用不同的类型使用C结构和元组结构体的区别在于要给结构内的每个字段给予名字来表明其意义。
#### 1.2 经典的C结构体的定义和初始化
- 单元结构体的定义和初始化
结构体内的每个字段都是命名字段,使得访问和修改更直观。
这种结构体没有任何字段。它们通常用于实现特定的行为,而不是表示数据。
```rust
// 定义结构体
struct Dummy;
```rust
// 定义结构体
struct Person {
name: String,
age: u32,
height: f32,
}
// 结构体实例化
let dummy = Dummy;
```
// 结构体实例化
let alice = Person {
name: String::from("Alice"),
age: 30,
height: 1.65,
};
```
>思考结构体内的数据可以用不同的类型使用C结构和元组结构体的区别在于要给结构内的每个字段给予名字来表明其意义。
#### 1.3 单元结构体的定义和初始化
这种结构体没有任何字段。它们通常用于实现特定的行为,而不是表示数据。
```rust
// 定义结构体
struct Dummy;
// 结构体实例化
let dummy = Dummy;
```
### 2. 方法
这三类结构体的方法的定义和使用方式相同,示例如下:
- 元组结构体的方法
```rust
struct Color(u8, u8, u8);
impl Color {
fn print(&self) {
println!("Color: ({}, {}, {})", self.0, self.1, self.2);
}
#### 2.1 元组结构体的方法
```rust
struct Color(u8, u8, u8);
impl Color {
fn print(&self) {
println!("Color: ({}, {}, {})", self.0, self.1, self.2);
}
}
let red = Color(255, 0, 0);
red.print(); // 输出Color: (255, 0, 0)
```
- 类C结构体的方法
```rust
struct Person {
name: String,
age: u32,
let red = Color(255, 0, 0);
red.print(); // 输出Color: (255, 0, 0)
```
#### 2.2 类C结构体的方法
```rust
struct Person {
name: String,
age: u32,
}
impl Person {
fn greet(&self) {
println!("Hello, my name is {} and I'm {} years old.", self.name, self.age);
}
}
impl Person {
fn greet(&self) {
println!("Hello, my name is {} and I'm {} years old.", self.name, self.age);
}
let alice = Person {
name: String::from("Alice"),
age: 30,
};
alice.greet(); // 输出Hello, my name is Alice and I'm 30 years old.
```
#### 2.3 单元结构体的方法
```rust
struct Dummy;
impl Dummy {
fn do_something(&self) {
println!("Doing something...");
}
}
let alice = Person {
name: String::from("Alice"),
age: 30,
};
alice.greet(); // 输出Hello, my name is Alice and I'm 30 years old.
```
- 单元结构体的方法
```rust
struct Dummy;
impl Dummy {
fn do_something(&self) {
println!("Doing something...");
}
}
let dummy_instance = Dummy;
dummy_instance.do_something(); // 输出Doing something...
```
let dummy_instance = Dummy;
dummy_instance.do_something(); // 输出Doing something...
```
## 3.7.2 枚举类型
### 1. 定义和实例化
- 枚举的定义
枚举允许在一个数据类型中定义多个变量。这在表示多种可能情况时非常有用。每个枚举成员可以具有关联的数据。
```rust
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(u8, u8, u8),
}
```
- 枚举的实例化
#### 1.1 枚举的定义
要创建枚举的实例,需要指定要使用的成员以及其关联的数据(如果有)
```rust
let msg = Message::Write(String::from("Hello, Rust!"));
```
枚举允许在一个数据类型中定义多个变量。这在表示多种可能情况时非常有用。每个枚举成员可以具有关联的数据
```rust
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(u8, u8, u8),
}
```
#### 1.2 枚举的实例化
要创建枚举的实例,需要指定要使用的成员以及其关联的数据(如果有)。
```rust
let msg = Message::Write(String::from("Hello, Rust!"));
```
### 2. 方法
与结构体类似,也可以为枚举定义方法。
```rust
impl Message {
fn process(&self) {
@@ -168,135 +182,144 @@ impl Message {
msg.process(); // 输出Write: Hello, Rust!
```
### 3. 控制流
- Match
#### 3.1 Match
Rust 中有一个特殊的控制流结构,叫做 match。它用于匹配枚举成员并针对每个成员执行相应的代码。
```rust
match msg {
Message::Quit => println!("Quit"),
Message::Move { x, y } => println!("Move to ({}, {})", x, y),
Message::Write(text) => println!("Write: {}", text),
Message::ChangeColor(r, g, b) => println!("Change color to ({}, {}, {})", r, g, b),
Rust 中有一个特殊的控制流结构,叫做 match。它用于匹配枚举成员并针对每个成员执行相应的代码。
```rust
match msg {
Message::Quit => println!("Quit"),
Message::Move { x, y } => println!("Move to ({}, {})", x, y),
Message::Write(text) => println!("Write: {}", text),
Message::ChangeColor(r, g, b) => println!("Change color to ({}, {}, {})", r, g, b),
}
```
match 表达式需要穷举所有可能的枚举成员,这有助于确保代码的完整性和安全性。在某些情况下,如果不需要处理所有枚举成员,可以使用 `_` 通配符来匹配任何未明确指定的成员。
```rust
match msg {
Message::Write(text) => println!("Write: {}", text),
_ => println!("Other message"),
}
```
#### 3.2 `if let`
除了 match 语句之外Rust 还提供了 `if let` 语法,用于简化某些模式匹配的情况。`if let` 对于只关心单个枚举变体的情况特别有用,这样可以避免编写繁琐的 `match` 语句。`if let` 可以将值解构为变量,并在匹配成功时执行代码块。
下面是一个使用 `Option` 枚举的示例:
```rust
fn main() {
let some_number = Some(42);
// 使用 match 语句
match some_number {
Some(x) => println!("The number is {}", x),
_ => (),
}
```
match 表达式需要穷举所有可能的枚举成员,这有助于确保代码的完整性和安全性。在某些情况下,如果不需要处理所有枚举成员,可以使用 _ 通配符来匹配任何未明确指定的成员。
```rust
match msg {
Message::Write(text) => println!("Write: {}", text),
_ => println!("Other message"),
// 使用 if let 语句
if let Some(x) = some_number {
println!("The number is {}", x);
}
```
- `if let`
}
```
除了 match 语句之外Rust 还提供了 `if let` 语法,用于简化某些模式匹配的情况。`if let` 对于只关心单个枚举变体的情况特别有用,这样可以避免编写繁琐的 `match` 语句。`if let` 可以将值解构为变量,并在匹配成功时执行代码块
在这个示例中,`if let` 语法让代码更简洁,因为只关心 `Some` 变体。这里还可以使用 `else` 子句处理未匹配的情况
下面是一个使用 `Option` 枚举的示例:
```rust
fn main() {
let some_number = Some(42);
```rust
fn main() {
let some_number: Option<i32> = None;
// 使用 match 语句
match some_number {
Some(x) => println!("The number is {}", x),
_ => (),
}
// 使用 if let 语句
if let Some(x) = some_number {
println!("The number is {}", x);
}
if let Some(x) = some_number {
println!("The number is {}", x);
} else {
println!("No number found");
}
```
}
```
在这个示例中,`if let` 语法让代码更简洁,因为只关心 `Some` 变体。这里还可以使用 `else` 子句处理未匹配的情况
```rust
fn main() {
let some_number: Option<i32> = None;
在此示例中,由于 `some_number``None``if let` 语句不匹配,因此将执行 `else` 子句,输出 `"No number found"`
if let Some(x) = some_number {
println!("The number is {}", x);
} else {
println!("No number found");
}
`if let` 可以与 `Result` 枚举一起使用,以便更简洁地处理错误。当只关心 `Ok``Err` 变体之一时,这特别有用。以下是一个处理 `Result` 枚举的示例。
定义一个可能返回错误的函数:
```rust
fn divide(numerator: f64, denominator: f64) -> Result<f64, String> {
if denominator == 0.0 {
Err(String::from("Cannot divide by zero"))
} else {
Ok(numerator / denominator)
}
```
}
```
在此示例中,由于 `some_number` 是 `None``if let` 语句不匹配,因此将执行 `else` 子句,输出 `"No number found"`。
接下来,我们使用 `if let` 处理成功的情况:
`if let` 可以与 `Result` 枚举一起使用,以便更简洁地处理错误。当只关心 `Ok` 或 `Err` 变体之一时,这特别有用。以下是一个处理 `Result` 枚举的示例。
定义一个可能返回错误的函数:
```rust
fn divide(numerator: f64, denominator: f64) -> Result<f64, String> {
if denominator == 0.0 {
Err(String::from("Cannot divide by zero"))
} else {
Ok(numerator / denominator)
}
}
```
```rust
let result = divide(4.0, 2.0);
接下来,我们使用 `if let` 处理成功的情况:
```rust
let result = divide(4.0, 2.0);
if let Ok(value) = result {
println!("The result is {}", value);
}
```
if let Ok(value) = result {
println!("The result is {}", value);
}
```
在这种情况下,由于除法操作成功,`if let` 语句将匹配 `Ok` 变体,并输出结果。如果只关心错误情况,可以使用 `if let` 匹配 `Err` 变体:
在这种情况下,由于除法操作成功,`if let` 语句将匹配 `Ok` 变体,并输出结果。如果只关心错误情况,可以使用 `if let` 匹配 `Err` 变体:
```rust
let result = divide(4.0, 0.0);
```rust
let result = divide(4.0, 0.0);
if let Err(error) = result {
println!("Error: {}", error);
}
```
if let Err(error) = result {
println!("Error: {}", error);
}
```
在这种情况下,由于除法操作失败,`if let` 语句将匹配 `Err` 变体,并输出错误消息。
使用 `if let` 处理 `Result` 可以简化错误处理,特别是当只关心 `Ok` 或 `Err` 变体之一时。然而,请注意,对于更复杂的错误处理逻辑,`match` 语句或 `?` 运算符可能更适合。
在这种情况下,由于除法操作失败,`if let` 语句将匹配 `Err` 变体,并输出错误消息。
使用 `if let` 处理 `Result` 可以简化错误处理,特别是当只关心 `Ok``Err` 变体之一时。然而,请注意,对于更复杂的错误处理逻辑,`match` 语句或 `?` 运算符可能更适合。
### 4. 常用的枚举类型
Rust 标准库中有一些常用的枚举类型,例如 `Option``Result`
- Option表示一个值可能存在或不存在。其成员为 `Some(T)`(其中 `T` 是某种类型)和 `None`。
```rust
fn divide(numerator: f64, denominator: f64) -> Option<f64> {
if denominator == 0.0f64 {
None
} else {
Some(numerator / denominator)
}
}
#### 4.1 Option表示一个值可能存在或不存在。其成员为 `Some(T)`(其中 `T` 是某种类型)和 `None`。
let result = divide(4.0, 2.0);
match result {
Some(value) => println!("The result is {}", value),
None => println!("Cannot divide by zero"),
```rust
fn divide(numerator: f64, denominator: f64) -> Option<f64> {
if denominator == 0.0f64 {
None
} else {
Some(numerator / denominator)
}
```
}
- `Result`:表示一个操作可能成功或失败。其成员为 `Ok(T)`(其中 `T` 是某种类型)和 `Err(E)`(其中 `E` 是错误类型)。
let result = divide(4.0, 2.0);
match result {
Some(value) => println!("The result is {}", value),
None => println!("Cannot divide by zero"),
}
```
```rust
fn divide_result(numerator: f64, denominator: f64) -> Result<f64, String> {
if denominator == 0.0f64 {
Err(String::from("Cannot divide by zero"))
} else {
Ok(numerator / denominator)
}
#### 4.2 `Result`:表示一个操作可能成功或失败。其成员为 `Ok(T)`(其中 `T` 是某种类型)和 `Err(E)`(其中 `E` 是错误类型)。
```rust
fn divide_result(numerator: f64, denominator: f64) -> Result<f64, String> {
if denominator == 0.0f64 {
Err(String::from("Cannot divide by zero"))
} else {
Ok(numerator / denominator)
}
}
let result = divide_result(4.0, 0.0);
match result {
Ok(value) => println!("The result is {}", value),
Err(error) => println!("Error: {}", error),
}
```
let result = divide_result(4.0, 0.0);
match result {
Ok(value) => println!("The result is {}", value),
Err(error) => println!("Error: {}", error),
}
```
这些枚举类型有助于更安全地处理可能出现的错误情况,避免在代码中使用不安全的值(如空指针)。

View File

@@ -5,6 +5,7 @@
## 3.9.1 函数定义中的泛型
如果没有泛型,当为不同的类型定义逻辑相同的函数时,可能如下:
```rust
fn return_i8(v: i8) -> i8 { v }
fn return_i16(v: i16) -> i16 { v }
@@ -27,7 +28,9 @@ fn main() {
let _h = return_u64(2u64);
}
```
使用泛型后,可以在函数定义时使用泛型,在调用函数的地方指定具体的类型,如下:
```rust
fn return_value<T>(v: T) -> T{ v }
@@ -47,6 +50,7 @@ fn main() {
## 3.9.2 结构体定义中的泛型
在结构体中使用泛型的示例如下:
```rust
#[derive(Debug)]
struct Point<T> {
@@ -64,6 +68,7 @@ fn main() {
```
也可以像如下方式使用:
```rust
#[derive(Debug)]
struct Point<T, U> { // Point的两个字段可以指定为不同的类型
@@ -82,21 +87,25 @@ fn main() {
## 3.9.3 枚举定义中的泛型
标准库的Option类型就是使用泛型的枚举类型其定义如下
标准库的`Option`类型就是使用泛型的枚举类型,其定义如下:
```rust
enum Option<T> {
Some(T),
None,
}
```
同样的还有Result类型其定义如下
同样的还有`Result`类型,其定义如下:
```rust
enum Result<T, E> {
Ok(T),
Err(E),
}
```
下面再举一个枚举类型中使用泛型的例子:
```rust
enum Message<T, U> {
Msg1(u32),
@@ -116,6 +125,7 @@ fn main() {
## 3.9.4 方法定义中的泛型
还可以在方法中使用泛型例子1
`
```rust
struct Point<T> {
x: T,
@@ -139,6 +149,7 @@ fn main() {
```
方法中的泛型不一定和结构体中的一样,示例如下:
```rust
struct Point<T, U> {
x: T,