简介:《生锈的书》即Rust编程语言的官方教程,涵盖了Rust语言的核心概念,包括所有权系统、生命周期、借用、结构体、枚举、函数与闭包、宏、模块系统、泛型、错误处理、并发与并行,以及生态系统与工具链。通过学习“rust-the-book-master”项目中的内容,学习者能深入掌握Rust语言,应用于系统级编程。
1. 所有权系统介绍与实践
1.1 Rust的所有权概念简介
Rust是一门注重安全性的系统编程语言,其中“所有权”系统是其核心特性之一。所有权机制确保了程序在并发环境下无数据竞争、无空悬指针且内存管理高效。理解这一机制,对于Rust开发者而言至关重要,因为它涉及到变量的创建、作用域、内存分配与释放等根本概念。
1.2 所有权的基本规则
Rust的所有权遵循三个基本规则: - Rust每个值都有一个唯一的“所有者”。 - 当所有者离开作用域时,其拥有的值将被丢弃。 - 不能拥有多个可变引用。
1.3 所有权在实践中的应用
在实践中,开发者通过转移所有权、通过引用与借用等方式进行高效的数据交互。例如,函数传递参数时默认使用值传递,但可以通过引用传递避免不必要的数据复制。这种机制既保证了数据在多线程环境下的安全,又提高了程序的性能。
这一章将引导读者从基础知识出发,逐步深入理解所有权机制,并通过实例演示如何在日常开发中应用这些原则。在接下来的章节中,我们将详细探讨生命周期机制,以及它是如何与所有权协同工作的。
2. 生命周期机制与代码实践
生命周期机制是Rust语言中独特且重要的概念,它有助于确保内存安全而不依赖垃圾回收器。生命周期的引入让开发者在编码时必须显式地考虑数据的生命周期,这与Rust的所有权系统相辅相成。
2.1 生命周期机制概述
2.1.1 生命周期的作用与意义
生命周期是一种借用检查器用来确保所有引用都是有效的引用的机制。Rust 中的每一个引用都有其生命周期,这意味着引用从开始起作用到停止作用的时间。生命周期的主要目的是防止悬垂引用,即确保在引用结束作用域之前所指向的数据不会被丢弃。
在Rust中,当我们将引用作为函数参数或函数返回值时,我们必须明确地声明引用的生命周期。生命周期的标注以撇号(')开始,后跟小写字母。例如, &i32
是一个指向整型的不可变引用, &'a i32
表示带有生命周期 'a
的不可变引用。
2.1.2 如何在实践中识别和管理生命周期
为了识别和管理生命周期,我们首先需要在代码中声明它们。考虑以下函数:
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
这个函数的目的是返回两个字符串切片中较长的一个。但是,它会导致编译错误,因为编译器无法确定返回的引用的生命周期。为了解决这个问题,我们可以使用生命周期参数来标注函数:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
通过这种方式,我们告诉Rust,函数 longest
返回的引用的生命周期与参数 x
和 y
中的生命周期相等。这保证了只要 x
和 y
有效,返回的引用也有效。
2.2 生命周期与所有权的交互
2.2.1 所有权转移对生命周期的影响
当所有权被转移时,原先拥有数据的变量变得不再有效,同时其引用也随之失效。理解所有权转移对生命周期的影响,可以帮助我们更好的管理内存。
举个例子:
fn main() {
let s1 = String::from("hello");
let s2 = take_ownership(s1);
// s1 这里不能使用,因为所有权转移给了 s2
}
fn take_ownership(s: String) -> String {
s
}
一旦 s1
传递给 take_ownership
函数,它就不再有效,而 s2
成为了新的拥有者。如果在所有权转移之后尝试使用 s1
,会导致编译错误。
2.2.2 借用规则在生命周期管理中的应用
借用规则用于确保借用的数据不会与数据的所有者同时被销毁。规则主要涉及不可变引用与可变引用:
- 不可变引用允许多个引用同时存在。
- 可变引用在同一时间只能有一个,且不能与不可变引用同时存在。
- 引用必须总是有效的。
考虑下面的代码:
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2);
let r3 = &mut s;
println!("{}", r3);
}
这段代码是有效的,因为我们在修改 s
之前没有使用过可变引用。如果有可变引用,其他任何引用都不能再被创建。这有助于防止数据竞争并维护数据的一致性。
2.3 生命周期标注与分析
2.3.1 生命周期标注的语法和规则
生命周期标注的语法是由撇号 '
开头后跟一个标识符,如 'a
。生命周期参数定义了引用的生命周期范围。
例如:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
在这个例子中, 'a
是生命周期参数,它指明了 x
和 y
以及返回值的生命周期。生命周期标注允许我们定义多个生命周期参数,并在函数签名中将引用与生命周期关联。
2.3.2 静态与动态生命周期分析技巧
在Rust中,有一个特殊的生命周期叫做静态( 'static
),它表示数据的生命周期与整个程序的生命周期一样长。
let s: &'static str = "I have a static lifetime.";
这段代码中的字符串字面量拥有 'static
生命周期,因为它被存储在程序的只读内存区域,并在整个程序的执行期间都存在。
动态生命周期指的是在编译时无法确定的生命周期。它们通常在运行时通过诸如堆分配等方式决定。分析动态生命周期涉及到理解程序如何在运行时处理内存,以及如何通过Rust的借用检查器在编译时进行验证。
生命周期标注通常在函数、结构体、方法和闭包中使用。在每个场景中,生命周期的规则略有不同,但基本原则是相同的。理解这些生命周期标注和规则有助于编写出更加安全和高效的Rust代码。
接下来,我们将深入探讨如何在结构体和方法中使用这些概念,以及如何通过它们来设计更加复杂的系统。
3. 借用和引用规则深入探索
在Rust编程语言中,借用和引用规则是所有权系统的一个核心组成部分,它们确保了内存安全的同时又不会牺牲灵活性。这一章节将深入分析引用的种类、借用规则的应用场景以及引用与指针之间的区别。
3.1 引用的种类与特点
3.1.1 不可变引用与可变引用的区别
在Rust中,引用可以是不可变的也可以是可变的。不可变引用允许你读取引用的数据,但不允许修改;而可变引用则允许你更改数据。这种区分是 Rust 确保内存安全的关键机制之一。
不可变引用的使用示例代码:
fn main() {
let s = String::from("hello");
let r1 = &s; // 不可变引用
println!("r1 is {}", r1);
}
可变引用的使用示例代码:
fn main() {
let mut s = String::from("hello");
let r2 = &mut s; // 可变引用
r2.push_str(", world");
println!("r2 is {}", r2);
}
3.1.2 引用的生命周期与作用域
引用的生命周期是指引用保持有效的时间段。Rust 通过借用检查器来确保所有引用在整个作用域中都是有效的。生命周期的概念是Rust安全性和性能的关键,它帮助避免了悬挂引用和数据竞争。
在定义函数时,你可以显式地声明引用的生命周期参数:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
在这个例子中, 'a
是一个生命周期参数,表示 x
和 y
以及返回值的生命周期至少要和 'a
一样长。
3.2 借用规则的应用场景
3.2.1 在集合类型中的借用模式
集合类型如 Vec
和 HashMap
在Rust中很常见,它们的元素可以是不可变或可变引用。对于集合类型,重要的是遵循借用规则,特别是当需要在迭代器上进行操作时。
使用不可变引用的示例代码:
fn main() {
let v = vec!["a", "b", "c"];
for &item in &v {
println!("{}", item);
}
}
在上面的例子中,我们创建了一个向量 v
并使用不可变引用进行迭代。
3.2.2 管理复杂数据结构的借用策略
当处理复杂的数据结构,如多层嵌套的结构体或包含动态大小类型(DST)的结构体时,借用规则能帮助你保持数据结构的完整性。
考虑一个具有嵌套引用的结构体:
struct Point {
x: i32,
y: i32,
}
struct ComplexPoint<'a> {
origin: &'a Point,
target: &'a Point,
}
在这个例子中, ComplexPoint
结构体包含两个 Point
结构体的不可变引用。这种设计允许 ComplexPoint
实例在不拥有数据的情况下借用数据。
3.3 引用与指针的区别
3.3.1 引用与原始指针的比较
引用在Rust中通常比原始指针更安全,因为引用的生命周期与作用域紧密相关,而原始指针则不具有这些限制。然而,在特定的低级操作或与C语言互操作时,原始指针仍然非常有用。
原始指针示例代码:
fn main() {
let mut num = 5;
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;
}
3.3.2 引用安全性的保证与优化
Rust通过借用检查器来强制执行引用规则,从而保证引用安全性。此外,编译器会在编译时尝试优化引用的使用,减少不必要的数据复制,并通过引用传递来保持高性能。
引用优化示例代码:
fn foo(v: &Vec<i32>) {
// 函数体内部使用引用 v
}
fn main() {
let v = vec![1, 2, 3];
foo(&v); // 使用引用传递 v
}
在这个例子中, foo
函数接受一个 Vec<i32>
类型的不可变引用。在 main
函数中,我们通过引用传递 v
到 foo
函数中,避免了数据的复制。这样不仅提高了程序的性能,也保持了代码的简洁。
通过本章节的深入讨论,我们可以看到借用和引用规则如何在Rust中被精确地应用和管理,不仅保证了内存安全,还提供了在复杂场景中使用的灵活性。下一章我们将继续探索Rust中的结构体定义与方法实现,进一步深入理解Rust的强大功能。
4. 结构体自定义与方法定义
在Rust编程语言中,结构体(Structs)是定义自定义数据类型的一种方式,它们允许我们将多个相关的数据项组合在一起。结构体提供了一种方式,使你能够命名和包装数据,以形成更加复杂的数据结构。此外,Rust还提供了方法(Methods),这是一种允许你定义数据结构上的行为的方式。在本章中,我们将深入探索如何声明和实例化结构体,以及如何定义与结构体相关的方法,并且我们会讨论结构体的高级特性。
4.1 结构体的声明与实例化
结构体作为一种自定义的复合数据类型,可以帮助我们组织和封装数据。在本小节中,我们将详细介绍结构体的声明、实例化方法,以及如何在结构体上实现方法。
4.1.1 定义结构体类型
定义结构体的过程首先需要指定结构体的名称,然后列举出结构体中的各个字段(Field)及其类型。让我们来看一个简单的例子:
struct Person {
first_name: String,
last_name: String,
age: u8,
}
fn main() {
let name = Person {
first_name: String::from("Alice"),
last_name: String::from("Smith"),
age: 25,
};
}
在上述代码中,我们定义了一个 Person
结构体,并且创建了一个 name
实例。每个 Person
实例都包含一个名为 first_name
的 String
类型字段、一个名为 last_name
的 String
类型字段和一个名为 age
的 u8
类型字段。结构体实例化时,字段的值按声明顺序指定。
4.1.2 实现结构体的方法
结构体方法的声明必须包含一个 self
参数,它指向结构体实例本身,这样方法就可以使用或修改实例的数据。方法可以定义在 impl
块中,如下所示:
impl Person {
// 方法定义
fn get_full_name(&self) -> String {
format!("{} {}", self.first_name, self.last_name)
}
}
fn main() {
let person = Person {
first_name: String::from("Bob"),
last_name: String::from("Lee"),
age: 30,
};
// 调用方法
println!("Full name: {}", person.get_full_name());
}
在上面的代码片段中,我们为 Person
结构体实现了一个 get_full_name
方法,该方法利用了 self
参数来访问实例中的 first_name
和 last_name
字段,并将它们拼接成完整的姓名字符串返回。
4.2 结构体与方法的关系
结构体与方法的结合,可以定义出更加丰富的数据行为和领域逻辑,这在软件开发中是非常关键的。在本小节中,我们将探索方法的接收者与作用域,以及结构体继承与组合模式。
4.2.1 方法的接收者与作用域
在Rust中,方法可以有不同类型的接收者。除了前面提到的 &self
,还有 &mut self
(用于可变引用)和 self: Box<Self>
(用于移动语义)等。这些不同的接收者允许方法在不同的上下文中对结构体实例进行操作。例如:
impl Person {
fn update_name(&mut self, new_name: String) {
self.first_name = new_name;
}
}
fn main() {
let mut person = Person {
first_name: String::from("Charlie"),
last_name: String::from("Doe"),
age: 20,
};
person.update_name(String::from("Chen"));
println!("First name: {}", person.first_name);
}
在这个例子中, update_name
方法接收一个可变引用 &mut self
,这意味着该方法可以修改 Person
实例。
4.2.2 结构体继承与组合模式
Rust不支持传统意义上的类继承,但是可以利用结构体的组合(Composition)和特性(Traits)来实现类似继承的行为。组合是指将一个结构体嵌入到另一个结构体中,如下例所示:
struct Address {
street: String,
city: String,
}
struct Person {
name: String,
address: Address,
}
impl Person {
fn get_address(&self) -> &Address {
&self.address
}
}
fn main() {
let address = Address {
street: String::from("123 Main St"),
city: String::from("Anytown"),
};
let person = Person {
name: String::from("Diana"),
address,
};
println!("Address: {}", person.get_address().street);
}
在这里, Person
结构体包含了另一个 Address
结构体的实例。 Person
可以使用 Address
结构体的行为,但它们保持独立。
4.3 高级结构体特性
Rust的结构体系统还包含一些高级特性,例如无主类型(Zero-sized types,ZSTs)和零大小类型,以及如何定制化结构体行为的方法。这些特性是Rust语言的基石之一,也是Rust区别于其它编程语言的独特之处。
4.3.1 无主类型和零大小类型
Rust允许定义没有存储空间的类型,称为无主类型或零大小类型(ZSTs)。这些类型在某些场景下非常有用,例如用于类型标记或元编程。下面是一个定义ZST的例子:
struct Unit;
struct Pair<T1, T2>(T1, T2);
fn main() {
let u = Unit;
let p = Pair(1, "Hello");
println!("Size of 'Unit' is {} bytes", std::mem::size_of_val(&u));
println!("Size of 'Pair' is {} bytes", std::mem::size_of_val(&p));
}
在这个例子中, Unit
类型没有字段,因此它的大小是0字节。尽管 Pair
类型有两个字段,但是由于Rust允许在某些情况下进行优化,该类型的大小可能小于两个字段的总和。
4.3.2 定制化结构体行为的方法
Rust通过特性和派生(Derive)宏为结构体提供了高度定制化行为的能力。例如,你可以通过派生宏为结构体实现 Debug
、 PartialEq
、 Clone
等特性。让我们看一个使用 Debug
特性的例子:
#[derive(Debug)]
struct Rectangle {
length: u32,
width: u32,
}
fn main() {
let rect = Rectangle { length: 5, width: 10 };
println!("Rectangle is {:?}", rect);
}
在这个例子中,通过在结构体声明前添加 #[derive(Debug)]
,我们告诉Rust编译器为 Rectangle
结构体生成 Debug
特征的实现。因此,我们可以使用格式化字符串 {:?}
来打印结构体的调试信息。
本章的深入探索为我们提供了结构体和方法定义的丰富知识,从基础到高级特性,包括声明、实例化、方法定义、作用域和定制化结构体行为的方法。这些技能将帮助Rust开发者构建复杂、类型安全且高效的系统级软件。
5. 枚举与模式匹配的艺术
5.1 枚举类型的定义与优势
5.1.1 枚举与单例模式的区别
枚举(enumerations)是Rust编程语言中一种强大且灵活的数据结构。与单例模式不同,枚举允许你列出一个值可能的所有可能的变体,而不是只有一个。这在很多情况下非常有用,特别是在需要表示一组固定选项时。
单例模式是一种设计模式,用于确保一个类只有一个实例,并提供对该实例的全局访问点。单例模式通常用于数据库连接管理,日志记录,资源管理等场景。在Rust中,你也可以通过枚举来实现单例模式的行为。
考虑一个配置管理器的例子,它可能只能是三种状态之一:开发(Development),测试(Testing)或生产(Production)。用单例模式实现,你需要定义一个类,这个类保证只有一个实例,并提供一个方法来返回这个实例。而使用枚举,你可以直接列出这三个状态,代码更为直观和简洁。
enum Environment {
Development,
Testing,
Production,
}
fn get_environment() -> Environment {
// 这里省略了根据环境变量或其他逻辑来选择环境的代码
Environment::Development
}
5.1.2 枚举在数据建模中的作用
枚举在数据建模中非常有用,特别是当你需要明确区分不同的数据状态时。在Rust中,枚举类型可以包含不同类型的值,这使得它们非常适合表示复杂的、异构的数据结构。
举个例子,假设你正在构建一个系统来处理不同类型的用户账户。你可以定义一个枚举类型来列出所有可能的账户类型,并为每种类型定义不同的数据结构。
enum AccountType {
Individual,
Business,
Guest,
}
struct IndividualAccount {
name: String,
address: String,
}
struct BusinessAccount {
company_name: String,
tax_id: String,
}
struct GuestAccount {
guest_id: String,
}
在这个例子中, AccountType
枚举清晰地区分了三种不同类型的账户。每种账户类型都有自己的结构体来存储特定的信息,这使得数据模型既灵活又易于管理。
5.2 模式匹配的原理与实践
5.2.1 模式匹配的类型与构造
模式匹配是Rust中的一个核心概念,它允许你检查一个值是否符合某种模式,并根据匹配的结果执行不同的操作。在Rust中,模式匹配通常与 match
语句一起使用,它会依次检查每一个模式,直到找到第一个匹配的。
模式匹配可以应用于不同类型的值,包括但不限于枚举、数字、字符、字符串、元组等。模式匹配不仅可以用来匹配值的形状,还可以用来绑定值到变量。
下面是一个使用 match
语句进行模式匹配的例子:
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn process_message(msg: Message) {
match msg {
Message::Quit => {
// 退出程序的代码
}
Message::Move { x, y } => {
// 处理移动操作的代码
}
Message::Write(text) => {
// 处理文本消息的代码
}
Message::ChangeColor(r, g, b) => {
// 处理颜色变更的代码
}
}
}
5.2.2 在实际编程中运用模式匹配
在实际编程中,模式匹配是一种非常强大的工具,它可以用来简化复杂的逻辑判断。例如,使用模式匹配可以更容易地处理复杂的条件语句,尤其是在需要检查多种不同的条件时。
考虑一个需要处理文件扩展名的场景。使用模式匹配,你可以轻松地根据不同的扩展名执行不同的逻辑:
fn process_file_extension(extension: &str) {
match extension {
"txt" => println!("处理文本文件"),
"jpg" | "png" => println!("处理图片文件"),
"zip" => println!("处理压缩文件"),
_ => println!("未知文件类型"),
}
}
这段代码根据文件扩展名执行不同的操作。使用 |
符号可以实现一个模式的"或"匹配,也就是当"jpg"或"png"出现时,都会执行同一个代码块。使用下划线 _
作为通配符模式,它匹配任何未明确指定的值。
5.3 高级匹配技术
5.3.1 嵌套模式与守卫子句
嵌套模式允许你在模式匹配中创建更复杂的结构。例如,你可以在一个 match
表达式中嵌套另一个 match
表达式,从而处理更深层次的结构。
守卫子句(guard clauses)则是一种在模式匹配中加入条件判断的技术。它允许你在 match
表达式中包含一个额外的 if
表达式,只有当模式匹配成功且条件为真时,相应的分支才会被执行。
fn process_number(number: u32) {
match number {
n if n % 2 == 0 => println!("{} is even", n),
n if n % 2 != 0 => println!("{} is odd", n),
_ => println!("Unknown number"),
}
}
在这个例子中,对于偶数和奇数的处理使用了守卫子句,而 _
匹配所有其他情况。
5.3.2 枚举与结构体的匹配策略
在处理包含枚举和结构体的复杂数据时,模式匹配同样可以发挥其强大的功能。对于这样的数据结构,可以使用模式匹配来解构枚举中的不同变体,同时提取结构体中的信息。
假设有一个 Result
枚举,它代表一个可能的错误或成功的结果。你可以使用模式匹配来同时处理 Result
中的值以及其中包含的结构体数据。
fn process_result(result: Result<i32, String>) {
match result {
Ok(value) => println!("处理成功,值为: {}", value),
Err(e) => println!("处理错误: {}", e),
}
}
在这个例子中, match
表达式检查 result
是否是 Ok
或 Err
。在 Ok
的情况下,模式匹配会将值绑定到 value
变量上,并打印出来。在 Err
的情况下,错误信息被绑定到变量 e
上。
通过熟练运用这些模式匹配技术,你可以编写更加清晰和简洁的代码,从而提高程序的可维护性和可读性。
6. 函数与闭包编写技巧
6.1 函数的定义与调用
6.1.1 函数签名与类型推断
在 Rust 中,函数的定义首先需要声明一个函数签名,它由函数名、参数列表和返回类型组成。类型推断是 Rust 的一个强大特性,它允许编译器在大多数情况下自动推断变量和函数的类型,从而使代码更加简洁。
fn add(a: i32, b: i32) -> i32 {
a + b
}
在上面的例子中, add
函数接受两个 i32
类型的参数,并返回它们的和,同样也是 i32
类型。如果你省略了返回类型,Rust 依然可以推断出来,但清晰地写出返回类型是一个好的习惯,尤其是当函数返回类型不是 ()
时。
6.1.2 变长参数与默认参数的用法
Rust 通过 ...
(剩余参数)来处理不定数量的参数。默认参数则需要使用 fn
关键字在函数体内定义。
fn add(a: i32, b: i32, more_numbers: ...i32) -> i32 {
let mut sum = a + b;
for &number in more_numbers.iter() {
sum += number;
}
sum
}
fn print_and_return(x: i32) -> i32 {
println!("The number is {}", x);
x
}
fn default_and_print(x: i32 = 42) {
println!("The default number is {}", x);
}
在上述示例中, add
函数可以接受任意数量的参数,并将它们全部相加。 print_and_return
函数将打印传入的数字并返回它。而 default_and_print
函数有一个默认参数 x
,当调用时未提供该参数,它将使用默认值 42
。
6.2 闭包的原理与应用
6.2.1 闭包的捕获机制与生命周期
闭包是匿名函数,它们可以捕获其所在环境中变量的引用或值。Rust 中的闭包会根据其使用方式来决定是借用还是移动变量。
fn main() {
let name = "Closure";
let print_name = || println!("Hello, {}", name);
print_name();
}
在上面的代码中,闭包 print_name
捕获了变量 name
的引用。Rust 会自动为闭包生成合适的签名,包括生命周期注解。
6.2.2 在数据处理中使用闭包
闭包在数据处理中非常有用,尤其是和迭代器一起使用时。
let numbers = vec![1, 2, 3, 4, 5];
let even_numbers = numbers.into_iter().filter(|x| x % 2 == 0).collect::<Vec<i32>>();
上面的代码展示了如何使用闭包过滤出 numbers
向量中的偶数。闭包 |x| x % 2 == 0
直接作用于 filter
方法的每个元素,优雅地实现了数据过滤的功能。
6.3 高级函数技巧
6.3.1 高阶函数与回调机制
Rust 支持高阶函数,这意味着函数可以接受其他函数作为参数或返回其他函数。回调机制在事件处理、异步编程和 GUI 编程中非常常见。
fn apply_function<F>(f: F, value: i32) -> i32
where
F: Fn(i32) -> i32,
{
f(value)
}
fn main() {
let result = apply_function(|x| x * 2, 10);
println!("Result: {}", result);
}
在这个例子中, apply_function
接受一个函数 f
和一个整数 value
,它将函数应用于值上。 apply_function
的类型约束 F: Fn(i32) -> i32
表明 F
是一个接受 i32
并返回 i32
的函数。
6.3.2 函数式编程在Rust中的实践
Rust 通过其强大的类型系统和模式匹配支持函数式编程。借助迭代器、闭包和所有权,Rust 提供了函数式编程的诸多特性,例如 map、filter、fold 等。
let numbers = vec![1, 2, 3, 4, 5];
let sum: i32 = numbers.iter().map(|x| x * 2).fold(0, |acc, x| acc + x);
println!("The sum of doubled numbers is {}", sum);
在这个例子中,我们使用 map
函数将每个元素乘以 2,然后使用 fold
函数将它们累加到一个初始值 0 中。这种链式操作是函数式编程中常见的模式。
简介:《生锈的书》即Rust编程语言的官方教程,涵盖了Rust语言的核心概念,包括所有权系统、生命周期、借用、结构体、枚举、函数与闭包、宏、模块系统、泛型、错误处理、并发与并行,以及生态系统与工具链。通过学习“rust-the-book-master”项目中的内容,学习者能深入掌握Rust语言,应用于系统级编程。