Skip to content

前言

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将数据和能力分开,通过不同的语法结构(类型)来管理
  • 使用structenum专注于数据结构的定义,让类型系统更清晰
  • 使用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 必须通过 matchif let 进行解构,适合描述一个实体的状态或分类.

4. 核心区别对比

特性structenum
数据关系聚合(所有字段都得有)互斥(只能选一个变体)
内存占用字段大小的总和(加上对齐)最大变体的大小 + 标记位
访问方式通过 . 直接访问字段必须通过 matchif let 进行解构
主要用途描述一个物体的属性描述一个物体的状态或分类

trait 和 impl 的区别

1. trait 是"定义规范",impl 是"具体实现"

项目traitimpl
作用定义接口(规范)提供实现
是否有函数体可以有/没有必须有
本质抽象具体

2. impltrait 定义时的内部结构一模一样

rust
impl/trait Type/TraitName {
├── fn xxx(&self)      → 方法(method)
├── fn xxx()           → 关联函数(associated function)
├── const XXX          → 关联常量
└── type XXX           → 关联类型
}

3. 内部差别

  1. 默认实现 (Default Implementation)
    • trait 中: 方法可以有大括号 { ... }(默认实现),也可以只有一个分号 ;(仅声明).
    • impl 中: 所有的函数必须提供完整的大括号 { ... }(具体的逻辑实现).
  2. 关联类型的赋值
    • trait 中: 通常写成 type Item;,这叫占位符(泛型关联类型).
    • impl 中: 必须写成 type Item = u32;,这叫具体化(给类型赋值).
  3. 权限与可见性
    • 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)]: 自动为类型实现 Debug trait,使其可以使用 {:?} 格式化输出.

泛型和 Trait 约束语法规则

1. 基础约束语法

语法1: Type: Trait + Trait2 + ... 语法2: where Type: Trait + Trait2 + ...

rust
// 单个 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>

rust
// 指定关联类型的具体值
fn process<I: Iterator<Item=i32>>(iter: I) { }

// 多个关联类型
fn handle<I: Iterator<Item=String> + Clone>(iter: I) { }

3. Trait 对象

语法: &dyn TraitBox<dyn Trait>

rust
// 动态分发
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

rust
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 TraitBox<dyn Trait>运行时多态
生命周期'lifetime + Type: Trait指定引用的生命周期

一般而言 Type: Trait 冒号后面是一个 Trait, Type=ConcreteType 等号后面是一个具体类型

基于 MIT 协议发布