From 0cca915101a54e94da4977b81f5cf664342205ec Mon Sep 17 00:00:00 2001 From: Davirain Date: Wed, 17 May 2023 21:03:42 +0800 Subject: [PATCH] add chapter_3_8,9, 13 --- src/SUMMARY.md | 3 + src/chapter_3/chapter_3_13.md | 278 +++++++++++++++++++++++++++++++ src/chapter_3/chapter_3_8.md | 302 ++++++++++++++++++++++++++++++++++ src/chapter_3/chapter_3_9.md | 167 +++++++++++++++++++ 4 files changed, 750 insertions(+) create mode 100644 src/chapter_3/chapter_3_13.md create mode 100644 src/chapter_3/chapter_3_8.md create mode 100644 src/chapter_3/chapter_3_9.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 857f1bc..80a32ce 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -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) diff --git a/src/chapter_3/chapter_3_13.md b/src/chapter_3/chapter_3_13.md new file mode 100644 index 0000000..66bd098 --- /dev/null +++ b/src/chapter_3/chapter_3_13.md @@ -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, 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 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(f: T) -> T +where + T: Fn(i32) -> i32, +{ + f +} +// T 要求实现FnMut +fn returns_closure2(f: T) -> T +where + T: FnMut(), +{ + f +} +// T 要求实现FnOnce +fn returns_closure3(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)); +} +``` diff --git a/src/chapter_3/chapter_3_8.md b/src/chapter_3/chapter_3_8.md new file mode 100644 index 0000000..f73d7ac --- /dev/null +++ b/src/chapter_3/chapter_3_8.md @@ -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 = 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 { + 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 { + 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 { + 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), + } + ``` +这些枚举类型有助于更安全地处理可能出现的错误情况,避免在代码中使用不安全的值(如空指针)。 diff --git a/src/chapter_3/chapter_3_9.md b/src/chapter_3/chapter_3_9.md new file mode 100644 index 0000000..7f0a076 --- /dev/null +++ b/src/chapter_3/chapter_3_9.md @@ -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(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 { + 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 { // 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 { + Some(T), + None, +} +``` +同样的还有Result类型,其定义如下: +```rust +enum Result { + Ok(T), + Err(E), +} +``` +下面再举一个枚举类型中使用泛型的例子: +```rust +enum Message { + Msg1(u32), + Msg2(T), + Msg3(U), +} + + + +fn main() { + let _msg1: Message = Message::Msg1(1u32); + let _msg2:Message = Message::Msg2(2u8); + let _msg3:Message = Message::Msg3("hello".to_string()); +} +``` + +## 3.9.4 方法定义中的泛型 + +还可以在方法中使用泛型,例子1: +```rust +struct Point { + x: T, + y: T, +} + +impl Point { + 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 { + x: T, + y: U, +} +impl Point { + fn mixup(self, other: Point) -> Point { + 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通过在编译时进行泛型代码的单态化来保证效率(就是在编译时,把泛型换成了具体的类型)。单态化是通过填充编译时使用的具体类型,将通用代码转换为特定代码的过程。