导图社区 精通RUST第2版
这是一篇关于精通RUST的思维导图,Rust是一种系统编程语言,它提供了无与伦比的安全性,同时兼具C和C++的高性能。Rust大大简化了并发程序的编写,并广泛应用于系统级编程、网络编程、嵌入式开发等领域。通过以上步骤和建议,您可以逐步提高自己的Rust编程技能,并享受Rust带来的内存安全和高效性能。
编辑于2024-04-04 15:14:48精通RUST
第1章 Rust入门
1.1 Rust是什么,以及为何需要
Rust是一种快速、高并发、安全且具有授权性的编程语言,最初由Graydon Hoare于2006年创造和发布。现在它是一种开源语言,主要由Mozilla团队和许多开源社区成员共同维护和开发。
Rust在GitHub上有开源开发的网址,它的发展势头非常迅猛。通过社区驱动的请求注解过程(Request For Comment, RFC)将新功能添加到语言中,并且任何人都可以在其中提交新的功能特性,然后在RFC文档中详细描述它们。之后就RFC寻求共识,如果达成共识,则该功能特性进入实施阶段。
Rust作为一门静态和强类型语言而存在。静态属性意味着编译器在编译时具有所有相关变量和类型的信息,并且在编译时会进行大量检查,在运行时只保留少量的类型检查。
1.2 安装Rust工具链
rustup.rs
1.3 Rust简介
1.3.1 基元类型
bool
这些是常见的布尔值,可以是真(true),也可以是假(false)。
char
字符
integer
该类型的特征在于位宽。Rust支持的最大长度是128位。
isize
尺寸可变的有符号整形(尺寸取决于底层指针大小)。
usize
尺寸可变的无符号整形(尺寸取决于底层指针大小)。
f32
f32位浮点型
f64
f64位浮点型
[T; N]
固定大小的数组,T表示元素类型,N表示元素数目,并且是编译期非负常数。
[T]
动态大小的连续序列的视图,T表示任意类型。
str
字符串切片,主要用做引用,即&str
(T, U, ..)
有限序列,T和U可以是不同类型。
fn(i32)->i32
一个接收i32类型参数并返回i32类型参数的函数。函数也有一种类型。
1.3.2 变量声明和不可变性
在Rust中,我们使用关键字let来声明变量。在初始化变量后,无法为变量分配其他值。如果稍后需要将变量指向其他变量(同一类型),则需要在其前面加上关键字mut。
1.3.3 函数
函数将一堆指令抽象为具名实体,稍后可以通过其他代码调用这些指令,并帮助用户管理复杂性。
fn add(a: u64, b: u64) -> u64 { a + b } fn main() { let a: u64 = 17; let b = 3; let result = add(a, b); println!("Result {}", result); }
函数基本上是返回值的表达式,默认情况下是()(unit)类型的值,这与C/C++中的void返回类型相似。
1.3.4 闭包
Rust支持闭包。闭包与函数类似,但具有声明它们的环境或作用域的更多信息。虽然函数具有与之关联的名称,闭包的定义没有,但可以将它们分配给变量。Rust类型推断的另一个优点是,在大多数情况下,你可以为没有类型的闭包指定参数。这是一个最简单的闭包"let my_closure = ||();"。我们定义了一个什么都不做的无参数闭包。然后我们可以通过my_closure()来调用它,这和函数类似。两个竖条“||”用于存放闭包参数,例如|a, b|。当Rust无法找出正确的类型时,有时需要指定参数类型(|a:u32|)。
fn main() { let doubler = |x| x * 2; let value = 5; let twice = doubler(value); println!("{} doubled is {}", value, twice); let big_closure = |b, c| { let z = b + c; z * twice }; let some_number = big_closure(1, 2); println!("Result from closure: {}", some_number); }
1.3.5 字符串
字符串是在任何编程语言中最常用的数据类型之一。在Rust中,它们通常以两种形式出现:&str类型和String类型。Rust字符串保证是有效的UTF-8编码字节序列。它们不像C字符串那样以空值(NULL)终止,并且可以在字符串之间包含空的字节。
fn main() { let question = "How are you ?"; let person: String = "Bob".to_string(); let namaste = String::from("zd"); println!("{}! {} {}", namaste, question, person); }
在上述代码中,person和namaste的类型是String,而question的类型为&str。创建String类型数据的方法有多种。String类型数据是在堆上分配的,&str类型数据通常是指向现有字符串的指针,这些字符串在以在堆栈和堆上,也可以是已编译对象代码的数据段中的字符串。
1.3.6 条件和判断
Rust中的条件判断和其他语言中的类似,它们也遵循类C语言风格的if else结构。
fn main() { let rust_is_awesome = true; if rust_is_awesome { println!("Indeed"); } else { println!("Well, you should try Rust !"); } }
在Rust中,if构造不是语句,而是一个表达式。在一般的编程用语中,语句不返回任何值,但表达式会返回值。这种区别意味着Rust中的if else条件总是会返回一个值。该值可以是empty类型的(),也可能是实际的值。无论花括号的最后一行是什么,都会成为if else表达式的返回值。重点要注意if和else分支应该具有相同的返回类型。
fn main() { let result = if 1 == 2 { "Wait, what ?" } else { "Rust makes sense" }; println!("You know what ? {}.", result); }
当将要分配的值从if else表达式返回时,我们需要用分号作为结束标志。例如,if是表达式,那么let是一个声明,期望我们在结尾处有分号。
1.3.7 match表达式
Rust的match(匹配)表达式非常简单、易用。它基本上类似于C语言的switch语句简化版,允许用户根据变量的值,以及是否有高级过滤功能做出判断。
fn req_status() -> u32 { 200 } fn main() { let status = req_status(); match status { 200 => println!("Success"), 404 => println!("Not Found"), other => { println!("Request failed with code: {}", other); } } }
每个匹配必须返回相同的类型。此外,我们必须对所有可能匹配的值进行彻底匹配。Rust允许我们通过使用catch all变量(这里是other)或者_(下画线)来忽略其余的可能性。
1.3.8 循环
在Rust中重复做某些事情可以使用3种构造来完成,即loop、while和for。在所有这些构造中,通常都包含关键字continue和break,分别允许你跳过和跳出循环。
fn main() { let mut x = 1024; loop { if x < 0 { break; } println!("{} more runs to go", x); x -= 1; } }
在Rust中执行循环的一个额外特性是,能够使用名称标记循环代码块。这可以在你有两个或多个嵌套循环,并想要从它们中的任何一个中断的情况下使用,而不仅针对直接包含break语句的循环。
fn silly_sub(a: i32, b: i32) -> i32 { let mut result = 0; 'increment: loop { if result == a { let mut dec = b; 'decrement: loop { if dec == 0 { break 'increment; } else { result -= 1; dec -= 1; } } } else { result += 1; } } result } fn main() { let a = 10; let b = 4; let result = silly_sub(a, b); println!("{} minus {} is {}", a, b, result); }
Rust中也有关键字for,它类似于其他语言中使用的for循环,但它们的实现完全不同。Rust的for循环基本上是一种更强大的重复构造(迭代器)的语法糖。简单地说,Rust中的for循环只适用于可以转换为迭代器的类型。一种这样的类型是Range类型。Range类型可以指代一系列数字。
fn main() { print!("Normal range: "); for i in 0..10 { print!("{},", i); } println!(); print!("Inclusive range: "); for i in 0..=10 { print!("(),", i); } }
1.3.9 自定义数据类型
自定义类型,是由用户定义的类型。自定义类型可以由几种类型组成。它们可以是基元类型的包装器,也可以是多个自定义类型的组合。它们有3种形式:结构体、枚举及联合,或者称为struct、enum及union。它们允许你更轻松地表示自己的数据。自定义类型的命名规则遵循驼峰命名法(CamelCase)。
结构体
在Rust中,结构体声明形式有3种。其中最简单的是单元结构体(unit struct),它使用关键字struct进行声明,随后是其名称,并用分号作为结尾。
struct Dummy; fn main() { let value = Dummy; }
结构体的第2种形式是元组结构体(tuple struct),它具有关联数据。其中的每个字段都没有命名,而是根据它们在定义中的位置进行引用。
struct Color(u8, u8, u8); fn main() { let white = Color(255, 255, 255); let red = white.0; let green = white.1; let blue = white.2; println!("Red value: {}", red); let orange = Color(255, 165, 0); let Color(r, g, b) = orange; println!("R: {}, G: {}, B: {} (orange)", r, g, b); let Color(r, _, b) = orange; }
对于5个以下的属性进行数据建模时,元组结构是理想的选择。除此之外的任何选择都会妨碍代码的可读性和推理。对于具有3个以上的字段的数据类型,建设使用类C语言的结构体,这是第3种形式,也是最常用的形式。
struct Player { name: String, iq: u8, friends: u8, score: u16 } fn bump_player_score(mut player: Player, score: u16) { player.score += score; println!("Updated player stats:"); } fn main() { let name = "Alice".to_string(); let player = Player { name, iq: 171, friends: 134, score: 1129 }; bump_player_score(player, 120); }
枚举
当你需要为不同类型的东西建模时,枚举是一种好办法。它使用关键字enum创建,之后跟着的是枚举名称和一对花括号。在花括号内部,我们可以编写所有可能的类型,即变体。这些变体可以在包含或不包含数据的情况下定义,并且包含的数据可以是任何甚远类型、结构体、元组结构体,甚至是枚举类型。
enum Direction { N, E, S, W } enum PlayerAction { Move { direction: Direction, speed: u8 }, Wait, Attack(Direction) } fn main() { let simulated_player_action = PlayerAction::Move { direction: Direction::N, speed: 2, }; match simulated_player_action { PlayerAction::Wait => println!("Player wants to wait"), PlayerAction::Move { direction, speed } => { println!("Player wants to move in direction {:?} with speed {}", direction, speed) } PlayerAction::Attack(direction) => { println!("Player wants to accack direction {:?}", direction) } }; }
1.3.10 类型上的函数和方法
结构体上的impl块
我们可以使用两种机制向定义的结构体添加行为:一种是类似构造函数的函数,另一种是设置getter和setter方法。
struct Player { name: String, iq: u8, friends: u8 } impl Player { fn with_name(name: &str) -> Player { Player { name: name.to_string(), iq: 100, friends: 100 } } fn get_friends(&self) -> u8 { self.friends } fn set friends(&mut self, count: u8) { self.friends = count; } } fn main() { let mut player = Player::with_name("Dave"); player.set_friends(23); println!("{}'s friends count: {}", player.name, player.get_friends()); let _ = Player::get_friends(&player); }
关联方法:该方法没有self类型作为第1个参数。它类似于面向对象编程语言中的静态方法。这些方法在类型自身上即可调用,并且不需要类型的实例来调用。通过在方法名称前加上结构体名称和双冒号来调用关联方法。
实例方法:将self作为第1外参数的函数。这里的self类似于Python中的self,并且指向实现该方法的实例。
impl块和枚举
枚举广泛用于状态机,当其与match表达式搭配使用时,可使状态转换代码非常简洁。它们还可用于自定义错误类型的建模。
enum PaymentMode { Debit, Credit, Paypal } fn pay_by_credit(amt: u64) { println!("Processing credit payment of {}", amt); } fn pay_by_debit(amt: u64) { println!("Processing debit payment of {}", amt); } fn paypal_redirect(amt: u64) { println!("Redirecting to paypal for amount: {}", amt); } impl PaymentMode { fn pay(&self, amount: u64) { match self { PaymentMode::Debit => pay_by_debit(amount), PaymentMode::Credit => pay_by_credit(amount), PaymentMode::Paypal => paypal_redirect(amount) } } } fn get_saved_payment_mode() -> PaymentMode { PaymentMode::Debit } fn main() { let payment_mode = get_saved_payment_mode(); payment_mode.pay(512); }
1.3.11 module、import和use语句
编程语言通常会提供一种将大型代码块拆分为多个文件以管理复杂性的方法。Java遵循每个.java文件就是公共类的约定,而C++为我们提供了头文件和include语句。Rust提供了模块机制。模块是Rust程序中命名和组织代码的一种方式。
每个Rust程序都需要一个root模块。对于可执行文件,它通常是main.rs文件,对于程序库,它通常是lib.rs文件。
模块可以在其他模块内部声明,也可以组织为文件和目录。
为了让编译器能够识别我们的模块,需要使用关键字mod声明,在我们的root模块中,要在模块名称前使用关键字use,这表示将元素引入作用域。
模块中定义的元素默认是私有的,需要使用关键字pub将它暴露给调用方。
1.3.12 集合
数组
数组是有固定长度,可以存储相同类型的元素,它们用[T, N]表示,其中T表示任意类型,N表示数组元素的数量。数组的大小不能用变量表示,并且必须是usize的字面值(literal)。
元组
项目列表
键/值对
切片
1.3.13 迭代器
子主题
子主题
子主题
1.4 改进字符计数器
1.5 小结
第2章 使用Cargo管理项目
2.1 软件包管理器
2.2 模块
2.2.1 嵌套模块
2.2.2 将文件用作模块
2.2.3 将目录用作模块
2.3 Cargo和程序库
2.3.1 新建一个Cargo项目
2.3.2 Cargo与依赖项
2.3.3 使用Cargo执行测试
2.3.4 使用Cargo运行示例
2.3.5 Cargo工作区
2.4 Cargo工具扩展
2.4.1 子命令和Cargo安装
2.4.2 使用clippy格式化代码
2.4.3 Cargo.toml清单文件简介
2.5 搭建Rust开发环境
2.6 使用Cargo构建imgtool程序
2.7 小结
第3章 测试、文档化和基准评估
3.1 测试的目的
3.2 组织测试
3.3 单元测试
3.3.1 第一个单元测试
3.3.2 运行测试
3.3.3 隔离测试代码
3.3.4 故障测试
3..3.5 忽略测试
3.4 集成测试
3.4.1 第一个集成测试
3.4.2 共享通用代码
3.5 文档
3.5.1 编写文档
3.5.2 生成和查看文档
3.5.3 托管文档
3.5.4 文档属性
3.5.5 文档化测试
3.6 基准
3.6.1 内置的微观基准工具
3.6.2 稳定版Rust上的基准测试
3.7 编写和测试软件包——逻辑门模拟器
3.8 CI集成测试与Travis CI
3.9 小结
第4章 类型、泛型和特征
4.1 类型系统及其重要性
4.2 泛型
4.2.1 创建泛型#
4.2.2 泛型实现
4.2.3 泛型应用
4.3 用特征抽象行为
4.3.1 特征
4.3.2 特征的多种形式
4.4 使用包泛型的特征——特征区间
4.4.1 类型上的特征区间
4.4.2 泛型函数和impl代码块上的特征区间
4.4.3 使用“+”特征组合为区间
4.4.4 特征区间与impl特征语法
4.5 标准库特征简介
4.6 使用特征对象实现真正的多态性
4.6.1 分发
4.6.2 特征对象
4.7 小结
第5章 内存管理和安全性
5.1 程序和内存
5.2 程序如何使用内存
5.3 内存管理及其分类
5.4 内存分配简介
5.4.1 堆栈
5.4.2 堆
5.5 内存管理的缺陷
5.6 内存安全性
5.7 内存安全三原则
5.7.1 所有权
5.7.2 通过特征复用类型
5.7.3 借用
5.7.4 基于借用规则的方法类型
5.7.5 生命周期
5.8 Rust中的指针类型
5.8.1 引用——安全的指针
5.8.2 原始指针
5.8.3 智能指针
5.8.4 引用计数的智能指针
5.8.5 内部可变性的应用
5.9 小结
第6章 异常处理
6.1 异常处理简介
6.2 可恢复的异常
6.2.1 Option
6.2.2 Result
6.3 Option/Result的组合
6.3.1 常见的组合器
6.3.2 组合器应用
6.3.3 Option和Result类型之间的转换
6.4 及早返回和运算符“?”
6.5 不可恢复的异常
6.6 自定义错误和Error特征
6.7 小结
第7章 高级概念
7.1 类型系统简介
7.1.1 代码块和表达式
7.1.2 let语句
7.1.3 循环作为表达式
7.1.4 数字类型中的类型清晰度和符号区分
7.1.5 类型推断
7.1.6 类型别名
7.2 字符串
7.2.1 包含所有权的字符串——String
7.2.2 借用字符串——&str
7.2.3 字符串切片和分块
7.2.4 在函数中使用字符串
7.2.5 字符串拼接
7.2.6 &str和String的应用场景
7.3 全局值
7.3.1 常量
7.3.2 静态值
7.3.3 编译期函数——const fn
7.3.4 通过lazy_static宏将静态值动态化
7.4 迭代器
7.5 高级类型
7.5.1 不定长类型
7.5.2 函数类型
7.5.3 never类型“!”和函数分发
7.5.4 联合
7.5.5 Cow
7.6 高级特征
7.6.1 Sized和?Sized
7.6.2 Borrow和AsRef
7.6.3 ToBoned
7.6.4 From和Into
7.6.5 特征对象和对象安全性
7.6.6 通用函数调用语法
7.6.7 特征规则
7.7 闭包进阶
7.7.1 Fn闭包
7.7.2 FnMut闭包
7.7.3 FnOnce闭包
7.8 结构体、枚举和特征中的常量
7.9 模块、路径和导入
7.9.1 导入
7.9.2 再次导入
7.9.3 隐私性
7.10 高级匹配模式和守护
7.10.1 匹配守护
7.10.2 高级let构建
7.11 强制类型转换
7.12 类型与内存
7.12.1 内存对齐
7.12.2 std::mem模块
7.13 使用serde进行序列化和反序列化
7.14 小结
第8章 并发
8.1 程序执行模型
8.2 并发
8.2.1 并发方法
8.2.2 缺陷
8.3 Rust中的并发
8.3.1 线程基础
8.3.2 自定义线程
8.3.3 访问线程中的数据
8.4 线程的并发模型
8.4.1 状态共享模型
8.4.2 互斥
8.4.3 通过Arc和Mutex实现共享可变性
8.4.4 通过消息传递进行通信
8.5 Rust中的线程安全
8.5.1 什么是线程安全
8.5.2 线程安全的特征
8.5.3 Send
8.5.4 Sync
8.6 使用actor模型实现并发
8.7 其他程序库
8.8 小结
第9章 宏与元编程
9.1 什么是元编程?
9.2 Rust宏的应用场景
9.3 Rust中的宏及其类型
9.4 使用macro_rules!创建宏
9.5 标准库中的内置宏
9.6 macro_rules!宏的标记类型
9.7 宏中的重复
9.8 宏的高级应用——为HashMap的初始化编写DSL
9.9 宏用例——编写测试
9.10 练习
9.11 过程宏
9.12 派生宏
9.13 高度宏程序
9.14 常用的过程宏软件包
9.15 小结
第10章 不安全的Rust和外部函数接口
10.1 安全与不安全
10.1.1 不安全的函数和代码块
10.1.2 不安全的特征和实现
10.2 在Rust中调用C代码
10.3 通过C语言调用Rust代码
10.4 在Rust使用外部C/C++程序库
10.5 使用PyO3构造原生Python扩展
10.6 在Rust中为Node.js创建原生扩展
10.7 小结
第11章 日志
11.1 日志记录及其重要性
11.2 日志记录框架的需求
11.3 日志记录框架及其特性
11.4 日志记录方法
11.4.1 非结构化日志记录
11.4.2 结构化日志记录
11.5 Rust中的日志记录
11.5.1 log——为Rust日志记录
11.5.2 env_logger
11.5.3 log4rs
11.5.4 使用slog进行结构化日志记录
11.6 小结
第12章 Rust与网络编程
12.1 网络编程简介
12.2 同步网络I/O
12.3 异步网络I/O
12.3.1 Rust中的异步抽象
12.3.2 构建异步的Redis服务器
12.4 小结
第13章 用Rust构建Web应用程序
13.1 Rust中的Web应用
13.2 用hyper进行HTTP通信
13.2.1 hyper服务器端API构建一个短网址服务
13.2.2 作为客户端的hyper——构建一个URL短网址客户端
13.2.3 Web框架
13.3 actix-web基础知识
13.4 使用actix-web构建一个书签API
13.5 小结
第14章 Rust与WebAssembly
14.1 数据持久性的重要性
14.2 SQLite
14.3 PostgreSQL
14.4 r2d2连接池
14.5 Postgres和diesel ORM
14.6 小结
第15章 Rust和WebAssembly
15.1 什么是WebAssmbly
15.2 WebAssembly的设计目标
15.3 WebAssembly入门
15.3.1 在线尝试
15.3.2 生成WebAssembly的方法
15.4 Rust和WebAssembly
15.4.1 wasm-bindgen
15.4.2 其他WebAssembly项目
15.5 小结
第16章 Rust与桌面应用
16.1 GUI开发简介
16.2 GTK+框架
16.3 通过gtk-rs构建一个新闻类桌面应用程序
16.4 练习
16.5 其他新兴的UI框架
16.6 小结
第17章 调试
17.1 调试简介
17.1.1 调试器基础
17.1.2 调试的先决条件
17.1.3 配置GDB
17.1.4 一个示例程序——buggie
17.1.5 GDB基础知识
17.1.6 在Visual Studio Code中集成GDB
17.2 rr调试器简介
17.3 小结