add chapter_3_8,9, 13

This commit is contained in:
Davirain
2023-05-17 21:03:42 +08:00
parent 9b405123f5
commit 0cca915101
4 changed files with 750 additions and 0 deletions

View File

@@ -13,6 +13,9 @@
- [所有权介绍](./chapter_3/chapter_3_7_1.md)
- [引用与借用](./chapter_3/chapter_3_7_2.md)
- [Slice类型](./chapter_3/chapter_3_7_3.md)
- [复合数据类型](./chapter_3/chapter_3_8.md)
- [泛型](./chapter_3/chapter_3_9.md)
- [3.13 闭包](./chapter_3/chapter_3_13.md)
- [3.14 迭代器](./chapter_3/chapter_3_14.md)
- [3.15 常见Collections](./chapter_3/chapter_3_15.md)
- [3.15.1 Vector](./chapter_3/chapter_3_15_1.md)

View File

@@ -0,0 +1,278 @@
# 3.13 闭包
## 3.13.1 闭包介绍
闭包是可以保存进变量或者作为参数传递给其它函数的匿名函数。闭包和函数不同的是,闭包允许捕获调用者作用域中的值。下面为使用闭包的简单示例:
```rust
fn main() {
let use_closure = || {
println!("This is a closure");
};
use_closure(); // 此行打印“This is a closure”
}
```
闭包有如下语法格式:
```rust
fn add_one_v1(x: u32) -> u32 { x + 1 } //函数
let add_one_v2 = |x: u32| -> u32 { x + 1 }; //闭包
let add_one_v3 = |x| { x + 1 }; //自动推导参数类型和返回值类型
let add_one_v4 = |x| x+1; //自动推导参数类型和返回值类型
```
闭包定义会为每个参数和返回类型推导一个具体类型,但是不能推导两次。下面是错误示例:
```rust
fn main() {
let example_closure = |x| x;
let s = example_closure(String::from("hello"));
let n = example_closure(5); //报错,尝试推导两次,变成了不同的类型
}
```
## 3.13.2 闭包捕获环境
下面的示例展示了闭包捕获环境中的变量:
```rust
fn main() {
let x = 4;
let equal_to_x = |z| z == x; //捕获环境中的值
let y = 4;
assert!(equal_to_x(y));
}
```
闭包可以通过三种方式捕获其环境,对应函数的三种获取参数的方式,分别是获取所有权、可变借用和不可变借用。
这三种捕获值的方式被编码为如下三个trait
- `FnOnce`:消费从周围作用域捕获变量(即获取捕获变量的所有权),闭包周围的作用域被称为其环境。为了消费捕获到的变量,闭包必须获取其所有权并将其移动进闭包。其名称的`Once`部分代表了闭包不能多次获取相同变量的所有权。
- `FnMut`:获取可变的借用值,所以可以改变其环境。
- `Fn`:从其环境获取不可变的借用值。
当创建一个闭包时Rust会根据其如何使用环境中的变量来推断如何引用环境。由于所有闭包都可以被调用至少一次因此所有闭包都实现了`FnOnce`。没有移动被捕获变量的所有权到闭包的闭包也实现了`FnMut`,而不需要对捕获的变量进行可变访问的闭包则实现了`Fn`
下面示例分别给出了实现三种Trait的闭包
```rust
fn call_once(c: impl FnOnce()) {
c();
}
fn call_mut(c: &mut impl FnMut()) {
c();
}
fn call_fn(c: impl Fn()) {
c();
}
fn main() {
// 1、闭包use_closure1只实现了FnOnce Trait只能被调用一次
let s = "Hello".to_string();
let use_closure1 = move || {
let s1 = s;
println!("s1 = {:?}", s1);
};
use_closure1(); // 此行打印“s1 = "Hello"”
// println!("s = {:?}", s); // 编译错误因为s所有权已经被移动闭包中use_closure1中
// use_closure1(); // 编译错误多次调用use_closure1出错
let s = "Hello".to_string();
let use_closure11 = move || {
let s1 = s;
println!("s1 = {:?}", s1);
};
call_once(use_closure11);
// 2、闭包use_closure2只实现了FnOnce Trait和FnMut Trait
let mut s = "Hello".to_string();
let mut use_closure2 = || {
s.push_str(", world!");
println!("s = {:?}", s);
};
use_closure2(); // 此行打印“s = "Hello, world!"”
use_closure2(); // 可以多次调用此行打印“s = "Hello, world!, world!"”
call_mut(&mut use_closure2);
call_once(use_closure2);
// 3、闭包use_closure3实现了FnOnce Trait、FnMut Trait和Fn Trait
let s = "Hello".to_string();
let mut use_closure3 = || {
println!("s = {:?}", s);
};
use_closure3(); // 此行打印“s = "Hello"”
use_closure3(); // 可以多次调用此行打印“s = "Hello!"”
call_fn(use_closure3);
call_mut(&mut use_closure3);
call_once(use_closure3);
}
```
## 3.13.3 作为参数和返回值
### 1. 函数指针
函数指针的使用可以让函数作为另一个函数的参数。函数的类型是`fn``fn`被称为函数指针。指定参数为函数指针的语法类似于闭包。
```rust
fn add_one(x: i32) -> i32 {
x + 1
}
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 { //第一个参数为函数指针
f(arg) + f(arg)
}
fn main() {
let answer = do_twice(add_one, 5);
println!("The answer is: {}", answer);
}
```
函数指针实现了闭包的三个trait`Fn``FnMut``FnOnce`),函数指针作为参数的地方也可以传入闭包。
### 2. 闭包作为参数和返回值
基于上面的1的知识可知闭包可以作为参数同样也可以作为返回值闭包作为参数的示例如下
```rust
fn wrapper_func<T>(t: T, v: i32) -> i32
where
T: Fn(i32) -> i32,
{
t(v)
}
fn func(v: i32) -> i32 {
v + 1
}
fn main() {
let a = wrapper_func(|x| x + 1, 1); // 闭包作为参数
println!("a = {}", a);
let b = wrapper_func(func, 1); // 函数作为参数
println!("b = {}", b);
}
```
闭包作为返回值的示例如下:
```rust
fn returns_closure() -> Box<dyn Fn(i32) -> i32> { // 返回的是trait对象
Box::new(|x| x + 1)
}
fn main() {
let c = returns_closure();
println!("r = {}", c(1)); //等价于println!("r = {}", (*c)(1));
}
```
需要注意的是函数定义时返回的是Box包含的trait对象因为编译器在编译时需要知道返回值的大小。所以对于下面两种`returns_closure`函数定义,编译器将报错:
```rust
// 错误方式一
fn returns_closure() -> dyn Fn(i32) -> i32 {
|x| x + 1
}
// 错误方式二
fn returns_closure() -> Fn(i32) -> i32 {
|x| x + 1
}
```
### 3. 闭包和泛型
闭包还可以和泛型结合在一起使用,示例如下:
```rust
// T 要求实现Fn
fn returns_closure1<T>(f: T) -> T
where
T: Fn(i32) -> i32,
{
f
}
// T 要求实现FnMut
fn returns_closure2<T>(f: T) -> T
where
T: FnMut(),
{
f
}
// T 要求实现FnOnce
fn returns_closure3<T>(f: T) -> T
where
T: FnOnce(),
{
f
}
fn main() {
let closure1 = |x| x + 1;
let c = returns_closure1(closure1);
println!("r = {}", c(1));
// T 实现了FnMut、FnOnce
let mut s = "Hello".to_string();
let closure2 = || {
s.push_str(", world!");
};
let mut c = returns_closure2(closure2);
c();
println!("s: {:?}", s);
let s = "Hello".to_string();
let closure3 = move || {
let s1 = s;
println!("s = {:?}", s1);
};
let c = returns_closure3(closure3);
c();
}
```
## 3.13.4 闭包背后的原理
Rust中的闭包是通过一个特殊的结构体实现的。具体来说每个闭包都是一个结构体对象其中包含了闭包的代码和环境中捕获的变量。这个结构体对象实现了一个或多个trait以便可以像函数一样使用它。
当定义一个闭包时Rust编译器会根据闭包的代码和捕获的变量生成一个结构体类型这个结构体类型实现了对应的trait。例如以下代码定义了一个闭包`add_x`并调用它:
```rust
fn main() {
let x = 10;
let add_x = |y| x + y; // 闭包
println!("{}", add_x(5)); // 调用闭包
}
```
在编译时Rust编译器会将这个闭包`add_x`转换为如下的结构体类型:
```rust
struct Closure<'a> {
x: i32,
y: i32,
}
impl<'a> FnOnce<(i32,)> for Closure<'a> {
type Output = i32;
fn call_once(self, args: (i32,)) -> i32 {
self.x + args.0
}
}
impl<'a> FnMut<(i32,)> for Closure<'a> {
fn call_mut(&mut self, args: (i32,)) -> i32 {
self.x + args.0
}
}
impl<'a> Fn<(i32,)> for Closure<'a> {
extern "rust-call" fn call(&self, args: (i32,)) -> i32 {
self.x + args.0
}
}
```
当闭包被调用时,它实际上是通过调用结构体的方法来执行的。所以调用闭包的代码就变成了如下:
```rust
fn main() {
let x = 10;
let mut add_x = Closure { x, y: 0 };
println!("{}", add_x(5));
}
```

View File

@@ -0,0 +1,302 @@
# 3.8 复合数据类型
Rust中可以通过结构体或者枚举来构造复杂的数据类型结构体使用struct 关键字枚举使用enum关键字。通过结构体或者枚举将多个值组合在一起。
## 3.8.1 结构体
结构体structure缩写成 struct有 3 种类型,使用 struct 关键字来创建:
- 元组结构体tuple struct事实上就是具名元组而已。
- 经典的 C 语言风格结构体C struct
- 单元结构体unit struct不带字段在泛型中很有用。
### 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);
#[derive(Debug, Eq, PartialEq)]
pub struct Point(i32, i32, i32);
fn main() {
assert_eq!(Color(1, 1, 1), Point(1, 1, 1)); // error 判断两个元组结构体是不是相等rust将不同类型名称的结构体看作是不一样的结构体虽然结构内的元素都是一样的。
}
```
- 经典的C结构体的定义和初始化
结构体内的每个字段都是命名字段,使得访问和修改更直观。
```rust
// 定义结构体
struct Person {
name: String,
age: u32,
height: f32,
}
// 结构体实例化
let alice = Person {
name: String::from("Alice"),
age: 30,
height: 1.65,
};
```
思考结构体内的数据可以用不同的类型使用C结构和元组结构体的区别在于要给结构内的每个字段给予名字来表明其意义。
- 单元结构体的定义和初始化
这种结构体没有任何字段。它们通常用于实现特定的行为,而不是表示数据。
```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);
}
}
let red = Color(255, 0, 0);
red.print(); // 输出Color: (255, 0, 0)
```
- 类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);
}
}
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...
```
## 3.7.2 枚举类型
### 1. 定义和实例化
- 枚举的定义
枚举允许在一个数据类型中定义多个变量。这在表示多种可能情况时非常有用。每个枚举成员可以具有关联的数据。
```rust
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(u8, u8, u8),
}
```
- 枚举的实例化
要创建枚举的实例,需要指定要使用的成员以及其关联的数据(如果有)。
```rust
let msg = Message::Write(String::from("Hello, Rust!"));
```
### 2. 方法
与结构体类似,也可以为枚举定义方法。
```rust
impl Message {
fn process(&self) {
match self {
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),
}
}
}
// 调用方法
msg.process(); // 输出Write: Hello, Rust!
```
### 3. 控制流
- 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),
}
```
match 表达式需要穷举所有可能的枚举成员,这有助于确保代码的完整性和安全性。在某些情况下,如果不需要处理所有枚举成员,可以使用 _ 通配符来匹配任何未明确指定的成员。
```rust
match msg {
Message::Write(text) => println!("Write: {}", text),
_ => println!("Other message"),
}
```
- `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),
_ => (),
}
// 使用 if let 语句
if let Some(x) = some_number {
println!("The number is {}", x);
}
}
```
在这个示例中,`if let` 语法让代码更简洁,因为只关心 `Some` 变体。这里还可以使用 `else` 子句处理未匹配的情况。
```rust
fn main() {
let some_number: Option<i32> = None;
if let Some(x) = some_number {
println!("The number is {}", x);
} else {
println!("No number found");
}
}
```
在此示例中,由于 `some_number` 是 `None``if let` 语句不匹配,因此将执行 `else` 子句,输出 `"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)
}
}
```
接下来,我们使用 `if let` 处理成功的情况:
```rust
let result = divide(4.0, 2.0);
if let Ok(value) = result {
println!("The result is {}", value);
}
```
在这种情况下,由于除法操作成功,`if let` 语句将匹配 `Ok` 变体,并输出结果。如果只关心错误情况,可以使用 `if let` 匹配 `Err` 变体:
```rust
let result = divide(4.0, 0.0);
if let Err(error) = result {
println!("Error: {}", error);
}
```
在这种情况下,由于除法操作失败,`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)
}
}
let result = divide(4.0, 2.0);
match result {
Some(value) => println!("The result is {}", value),
None => println!("Cannot divide by zero"),
}
```
- `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),
}
```
这些枚举类型有助于更安全地处理可能出现的错误情况,避免在代码中使用不安全的值(如空指针)。

View File

@@ -0,0 +1,167 @@
# 3.9 泛型
泛型是具体类型或者其它属性的抽象代替用于减少代码的重复。在编写Rust代码时可以用泛型来表示各种各样的数据类型等到编译阶段泛型则被替换成它所代表的的具体的数据类型。
## 3.9.1 函数定义中的泛型
如果没有泛型,当为不同的类型定义逻辑相同的函数时,可能如下:
```rust
fn return_i8(v: i8) -> i8 { v }
fn return_i16(v: i16) -> i16 { v }
fn return_i32(v: i32) -> i32 { v }
fn return_i64(v: i64) -> i64 { v }
fn return_u8(v: u8) -> u8 { v }
fn return_u16(v: u16) -> u16 { v }
fn return_u32(v: u32) -> u32 { v }
fn return_u64(v: u64) -> u64 { v }
fn main() {
let _a = return_i8(2i8);
let _b = return_i16(2i16);
let _c = return_i32(2i32);
let _d = return_i64(2i64);
let _e = return_u8(2u8);
let _f = return_u16(2u16);
let _g = return_u32(2u32);
let _h = return_u64(2u64);
}
```
使用泛型后,可以在函数定义时使用泛型,在调用函数的地方指定具体的类型,如下:
```rust
fn return_value<T>(v: T) -> T{ v }
fn main() {
let _a = return_value(2i8);
let _b = return_value(2i16);
let _c = return_value(2i32);
let _d = return_value(2i64);
let _e = return_value(2u8);
let _f = return_value(2u16);
let _g = return_value(2u32);
let _h = return_value(2u64);
}
```
## 3.9.2 结构体定义中的泛型
在结构体中使用泛型的示例如下:
```rust
#[derive(Debug)]
struct Point<T> {
x: T,
y: T,
}
fn main() {
let integer = Point { x: 1, y: 2 }; // Point的两个字段都是整型
println!("{:#?}", integer);
let float = Point { x: 0.99, y: 1.99 }; // Point的两个字段都是浮点型
println!("{:#?}", float);
}
```
也可以像如下方式使用:
```rust
#[derive(Debug)]
struct Point<T, U> { // Point的两个字段可以指定为不同的类型
x: T,
y: U,
}
fn main() {
let a = Point { x: 1, y: 2.0 };
println!("{:#?}", a);
let b = Point { x: 1, y: 1.99 };
println!("{:#?}", b);
}
```
## 3.9.3 枚举定义中的泛型
标准库的Option类型就是使用泛型的枚举类型其定义如下
```rust
enum Option<T> {
Some(T),
None,
}
```
同样的还有Result类型其定义如下
```rust
enum Result<T, E> {
Ok(T),
Err(E),
}
```
下面再举一个枚举类型中使用泛型的例子:
```rust
enum Message<T, U> {
Msg1(u32),
Msg2(T),
Msg3(U),
}
fn main() {
let _msg1: Message<u8, String> = Message::Msg1(1u32);
let _msg2:Message<u8, String> = Message::Msg2(2u8);
let _msg3:Message<u8, String> = Message::Msg3("hello".to_string());
}
```
## 3.9.4 方法定义中的泛型
还可以在方法中使用泛型例子1
```rust
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn get_x(&self) -> &T {
&self.x
}
fn get_y(&self) -> &T {
&self.y
}
}
fn main() {
let p = Point { x: 1, y: 2 };
println!("p.x = {}", p.get_x());
println!("p.y = {}", p.get_y());
}
```
方法中的泛型不一定和结构体中的一样,示例如下:
```rust
struct Point<T, U> {
x: T,
y: U,
}
impl<T, U> Point<T, U> {
fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
Point {
x: self.x,
y: other.y,
}
}
}
fn main() {
let p1 = Point { x: 5, y: 10.4 };
let p2 = Point { x: "Hello", y: 'c'};
let p3 = p1.mixup(p2); // 对应的T、U分别是整型和浮点型V、W则分别是字面字符串和字符类型
println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}
```
## 3.9.5 泛型代码的性能
在Rust中使用泛型并不会造成程序性能上的损失。因为Rust通过在编译时进行泛型代码的单态化来保证效率就是在编译时把泛型换成了具体的类型。单态化是通过填充编译时使用的具体类型将通用代码转换为特定代码的过程。