前言
Rust 八股文 -- 更适合中国人的 Rust 教程
本教程是根据官方 Rust 教程、视频资料和相关书籍 整理 而成,章节结构基本参照官方教程,但融入了合理的调整和补充。你可以独立阅读,也可以配合官方教程使用。某些章节加入了知识点补充和个人理解。
我的目标是让这个教程成为一个更符合中国人学习习惯的 Rust 入门资源,降低学习难度,提供更清晰的概念解释和逻辑顺序,也希望成为 Rust 学习者的枕边书,遇到概念上的问题随时查找.
学习前置条件
本教程不要求你有 C/C++ 等系统级语言基础,也不要求你深入理解内存堆栈。但完全零基础学习的难度会更高,某些知识点需要基础计算机科学知识作为前置知识。
为什么要整理这个教程
我本身喜欢做笔记,同时在学习的过程中发现了一些对初学者不友好的问题,本质原因是目前国内 Rust 的商业动力不够强,所有没有倾斜更多的资源,好了现在我来做"大善人"了🐶.
现有的 Rust 教程资源虽然众多,但存在几个问题:
- 中文资源较少,多数是翻译作品,表达方式不符合中文学习逻辑
- 大多教程默认读者有系统开发基础,缺乏必要的前置知识说明
- Rust 学习曲线陡峭,现有教程在重难点讲解上不够充分,尤其是最后几章的高级部分,很不友好
本教程采用中国教科书式的讲解方式:先阐明概念本质,再举例说明,同时根据知识逻辑重新梳理顺序,希望能降低学习难度。
本教程的主要优势
- 更易学:遵循"概念→原理→示例"的教学逻辑,符合中文学习习惯
- 更完整:覆盖 Rust 核心知识点,对难点章节进行补充和详细讲解
- 更清晰:直接剖析概念本质,加入辅助理解和总结,帮助理解设计理念
- 更逻辑:按照知识逻辑重新梳理顺序,帮助理解和记忆
建议和修改
个人难免有理解偏差和表达不清的地方,如果你发现了任何问题或者有更好的建议,欢迎提交 issue 或 pull request 来改进这个教程。
支持与贡献
如果你觉得这个教程对你有帮助,欢迎给它一个 star ⭐,或者分享给更多的 Rust 学习者。如果你想参与改进这个教程,也欢迎提交 pull request 或 issue 来贡献你的想法和修改。
前言部分是对整个教程的一般性的总结、补充和思考,可以先浏览一遍,看不懂没关系,后续章节会有更详细的讲解
思考
- rust 是一门使用一套严格规则来限制程序员的语言,由编译器进行监督,保证了最终程序运行的安全性和性能
vscode快捷输入
- 操作方法: 输入变量名后打
.,在建议列表中选择片段 - 常用后缀:
x.if(生成 if 块)、x.match(生成 match)、x.dbg(生成dbg!(x))
数据和行为的分层设计
struct/enum: 数据层impl/impl for: 行为层trait: 抽象层/默认行为层,类似ts的interface中定义方法,但rust的trait还可以提供默认实现struct: 数据类型+ 数据组织enum: 多种可能值,但同一时刻只能是其中一种impl Type: 为struct/enum等类型添加方法impl Trait for Type: 为类型实现特定trait
思考:
- js的数据和能力都能直接以对象为载体进行挂载,非常自由,但也很混乱,不利于维护
- Rust将数据和能力分开,通过不同的语法结构(类型)来管理
- 使用
struct和enum专注于数据结构的定义,让类型系统更清晰 - 使用
impl Type来为struct/enum实现方法,可以让数据类型专注于数据结构 - 使用
impl Trait for Type则让能力的复用和抽象更清晰 - 同时Rust的设计也非常严谨,即使一个
struct自己实现了某种方法,在进入泛型函数时也不会认可这种方法,必须impl Trait再次实现/转发这种方法,这保证了能力的明确性和一致性
struct和enum的区别
简单来说,这两者的区别主要在于"和"与"或"的关系:
1. 逻辑本质
struct (结构体): 是数据的组合(And).
- 一个结构体实例同时拥有定义的每一个字段.
- 例子: 一个 User 既有 name 且 有 age.
enum (枚举): 是数据的选择(Or).
- 一个枚举实例在同一时刻只能是定义的其中一种变体.
- 例子: 一个 IpAddr 要么是 V4 要么是 V6.
2. 定义上区别
struct:{key: type, ...}定义了一组字段,每个字段都有一个名字和类型,实例化时必须提供所有字段的值.enum:- 单元变体:
enum Name { Variant1, Variant2, ... }定义了一组没有数据的变体,实例化时只需要指定变体名称. - 元组变体:
enum Name { Variant1(Type1, Type2),... }定义了一组变体,每个变体有一个或多个匿名字段,实例化时需要提供对应类型的值. - 结构体变体:
enum Name { Variant1 { key1: Type1, ...},... }定义了一组变体,每个变体可以有不同的字段,但同一时刻只能是其中一个.
- 单元变体:
3. 使用上区别
struct直接使用字段访问(user.name),适合描述一个实体的属性.enum必须通过match或if let进行解构,适合描述一个实体的状态或分类.
4. 核心区别对比
| 特性 | struct | enum |
|---|---|---|
| 数据关系 | 聚合(所有字段都得有) | 互斥(只能选一个变体) |
| 内存占用 | 字段大小的总和(加上对齐) | 最大变体的大小 + 标记位 |
| 访问方式 | 通过 . 直接访问字段 | 必须通过 match 或 if let 进行解构 |
| 主要用途 | 描述一个物体的属性 | 描述一个物体的状态或分类 |
trait 和 impl 的区别
1. trait 是"定义规范",impl 是"具体实现"
| 项目 | trait | impl |
|---|---|---|
| 作用 | 定义接口(规范) | 提供实现 |
| 是否有函数体 | 可以有/没有 | 必须有 |
| 本质 | 抽象 | 具体 |
2. impl 和 trait 定义时的内部结构一模一样
impl/trait Type/TraitName {
├── fn xxx(&self) → 方法(method)
├── fn xxx() → 关联函数(associated function)
├── const XXX → 关联常量
└── type XXX → 关联类型
}3. 内部差别
- 默认实现 (Default Implementation)
trait中: 方法可以有大括号{ ... }(默认实现),也可以只有一个分号;(仅声明).impl中: 所有的函数必须提供完整的大括号{ ... }(具体的逻辑实现).
- 关联类型的赋值
trait中: 通常写成type Item;,这叫占位符(泛型关联类型).impl中: 必须写成type Item = u32;,这叫具体化(给类型赋值).
- 权限与可见性
trait中: 所有的项默认都是公开的(因为 Trait 的本质就是接口).impl中:- 如果是
impl Trait for Type,里面的项跟随 Trait 的公开属性. - 如果是
impl Type(固有实现),方法前可以加pub或不加(默认私有).
- 如果是
struct 和 trait
- 要使用
struct实现的trait方法,需要导入struct的同时也导入trait - 即使外部库已经为它的对象写好了
impl Trait for Struct,你在使用时也必须同时引入这两者 - Prelude 模式: 大多数成熟的 Rust 库(如 tokio、itertools、diesel)都会提供一个预导模块 (Prelude)rust
// 一次性把这个库最常用的所有 Trait 都引入进来 use some_library::prelude::*;
原因:
1. 避免方法名冲突(命名空间管理)
- 如果同时使用两个库,它们都给
String增加了parse()方法 - 如果 Rust 默认自动加载所有 Trait 方法,编译器就不知道你调用的是哪个
- 通过强制
use,你明确了想使用哪个库的功能
2. 编译性能优化
- 如果编译器每次处理文件都要扫描项目中成百上千个 Trait,寻找匹配的方法,编译速度会无法接受
- 显式导入只加载必要的 Trait,减少编译时间和内存占用
3. 鸭子类型的权衡
- Rust 采用显式导入而非隐式加载,确保代码的确定性和可读性
- 看到
use std::io::Read;就知道后续调用的read()方法来自该 Trait
self 和 Self 的区别
1. 大写的 Self —— 它是【类型】的替身
Self是一个类型别名.它指代当前正在实现Trait或方法的那个具体的类型.
2. 小写的 self —— 它是【实例】的替身
self是一个具体的对象(或者是对象的引用).它代表调用这个方法的那个变量本身.
derive 自动实现
#[derive(...)] 是 Rust 的一个属性宏,用于自动为一个类型实现某些trait.省去写重复代码的麻烦.它本质是一个派生宏,由编译器(或外部库)在编译时生成对应的impl块,实现了指定trait的功能.常见的派生trait包括:
Debug: 允许使用{:?}格式化输出类型的实例,适合调试.Clone: 允许创建类型实例的副本.Copy: 允许类型的实例在赋值或传递时进行位复制,适用于简单的标量类型.PartialEq: 允许使用==和!=比较类型的实例.Eq: 表示类型的实例可以进行完全相等比较,通常与PartialEq一起使用.Hash: 允许类型的实例被用作哈希表的键.Default: 允许为类型提供一个默认值.PartialOrd: 允许使用<、>、<=、>=进行部分有序比较(如f64因 NaN 只能偏序).Ord: 完全有序比较,通常与PartialOrd一起使用,实现.sort()等需完全排序的操作.
例如:
#[derive(Debug)]: 自动为类型实现Debugtrait,使其可以使用{:?}格式化输出.
泛型和 Trait 约束语法规则
1. 基础约束语法
语法1: Type: Trait + Trait2 + ... 语法2: where Type: Trait + Trait2 + ...
// 单个 Trait 约束
fn foo<T: Display>(x: T) { }
// 多个 Trait 约束(用 + 连接)
fn bar<T: Display + Debug>(x: T) { }
// Where 子句(复杂约束更清晰)
fn baz<T>(x: T) where T: Display + Debug { }2. 关联类型约束
语法: Type: Trait<AssociatedType=ConcreteType>
// 指定关联类型的具体值
fn process<I: Iterator<Item=i32>>(iter: I) { }
// 多个关联类型
fn handle<I: Iterator<Item=String> + Clone>(iter: I) { }3. Trait 对象
语法: &dyn Trait 或 Box<dyn Trait>
// 动态分发
fn print_it(val: &dyn Display) {
println!("{}", val);
}
// 返回 Trait 对象
fn get_drawable() -> Box<dyn Draw> {
Box::new(Circle { radius: 5.0 })
}4. 生命周期与 Trait 结合
语法: 'lifetime + Type: Trait
fn longest<'a, T: Display>(x: &'a T, y: &'a T) -> &'a T {
if x.to_string().len() > y.to_string().len() { x } else { y }
}5. 常见模式对比
| 场景 | 语法 | 用途 |
|---|---|---|
| 简单约束 | T: Trait | 单个约束,函数参数 |
| 复杂约束 | where T: Trait + Trait2 | 多个约束,提高可读性 |
| 关联类型 | I: Iterator<Item=u32> | 指定泛型的关联类型 |
| Trait 对象 | &dyn Trait 或 Box<dyn Trait> | 运行时多态 |
| 生命周期 | 'lifetime + Type: Trait | 指定引用的生命周期 |
一般而言
Type: Trait冒号后面是一个 Trait,Type=ConcreteType等号后面是一个具体类型