mirror of
https://github.com/RustyCab/LearnRustEasy.git
synced 2026-02-04 02:33:24 +08:00
add chapter_3_8,9, 13
This commit is contained in:
@@ -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)
|
||||
|
||||
278
src/chapter_3/chapter_3_13.md
Normal file
278
src/chapter_3/chapter_3_13.md
Normal 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));
|
||||
}
|
||||
```
|
||||
302
src/chapter_3/chapter_3_8.md
Normal file
302
src/chapter_3/chapter_3_8.md
Normal 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),
|
||||
}
|
||||
```
|
||||
这些枚举类型有助于更安全地处理可能出现的错误情况,避免在代码中使用不安全的值(如空指针)。
|
||||
167
src/chapter_3/chapter_3_9.md
Normal file
167
src/chapter_3/chapter_3_9.md
Normal 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通过在编译时进行泛型代码的单态化来保证效率(就是在编译时,把泛型换成了具体的类型)。单态化是通过填充编译时使用的具体类型,将通用代码转换为特定代码的过程。
|
||||
Reference in New Issue
Block a user