password
icon
AI summary
type
status
date
slug
summary
tags
category
0. 学习资料
生态极好,学习资源丰富,社区讨论度极高
- https://doc.rust-lang.org/book:甚至可以在线运行
- rustlings:在VsCode中练习,有很多练习题
- https://github.com/LearningOS:清华开源操作系统训练营
- 极客时间:Rust训练营
1. Java→Rust概念映射
Java 概念 | Rust 对应概念 | 核心差异 |
Class | Struct (数据) + Impl (行为) | Rust 数据与行为分离,没有继承,只有组合。 |
Interface | Trait | Trait 更强大,可以包含默认实现,甚至作为类型约束(Bounds)。 |
Null | Option<T> | Rust 没有 Null!强制你在编译期处理“空值”情况,彻底告别 NPE。 |
Exception | Result<T, E> | 错误是值,必须处理。没有 try-catch 的控制流跳转,逻辑更线性。 |
Annotation | Attribute (宏) | 如 #[derive(Debug)]。Rust 的宏是元编程,比 Java 注解更底层更强大。 |
Maven/Gradle | Cargo | 体验极佳的包管理和构建工具。 Cargo.toml 等同于 pom.xml。 |
2. 基础语法
参考:https://learnxinyminutes.com/rust/(新编程语言快速上手网站)
有些特别的点是:
变量:
1)不可变性
- 默认行为:Rust中所有通过let定义的绑定默认都是不可变的(immutable)
- 具体表现:例如 let x = 1; 后,不能执行 x = 2 这样的重新赋值操作
- 设计原理:这种设计保证了变量一旦绑定后,其值在后续访问时必定保持不变
2)可变变量
- 可变声明:通过添加mut关键字可声明可变变量,如 let mut x = 1;
- 重新赋值:可变变量允许后续修改,如 x = 2 的操作在mut声明下合法
使用建议:默认使用不可变绑定,仅在需要修改值时使用mut,这符合Rust的安全设计理念
字符串
1)字符串类型
- 字面量表示:使用引号包裹的文本可直接赋值给变量,如"hello",这种形式的类型是&str
- String类型:通过to_string()方法创建,如"hello world".to_string(),是具有所有权的字符串类型
- 内存分配:
- &str是引用类型,指向字符串数据的引用
- String类型数据存储在堆上,栈上保存指向堆数据的指针
2)类型转换与引用
- 引用关系:String类型可以通过引用转换为&str类型
- 所有权概念:String具有完整所有权,而&str只是借用引用(所有权概念将在后续详细讲解)
3)输出方法
- println!宏:
- 使用叹号!标识,表明是宏而非函数
- 编译时会被展开成一系列代码
- 可以打印数字、字符串等各种数据结构
- print!宏:与println!类似,但不自动换行
向量和数组
- 内存分配:
- 数组:分配在栈上,如示例[1,2,3,4]用方括号声明
- 向量:分配在堆上,通过栈指针引用堆内存
- 大小特性:
- 数组:固定大小和类型,不可动态增长
- 向量:动态增长结构,支持push操作(需声明为let mut vector)
- 初始化方式:
- 向量:使用vec!宏初始化,如vec![1,2,3]会依次push元素
- 视图访问:
- 向量引用可获得只读视图(类似String的&str),本质是包含起始指针和长度的胖指针
- 打印方式:
- 使用{}对应Display trait
- 使用{:?}对应Debug trait
元组
- 类型特性:
- 固定长度但元素类型可异构(对比数组要求同类型)
- 访问方式:
- 解构访问:类似JavaScript的解构语法
- 索引访问:通过.index形式,如x.1访问第二个元素(索引从0开始)
- 示例说明:
- 若x = (5, "hello"),则x.1输出字符串"hello"
自定义数据类型
1)结构体
- 基本形式: 使用struct关键字定义,包含命名字段,如
struct Point {x:i32, y:i32}
- 元组结构体: 无命名字段的结构体,如
struct Point2(i32, i32)
- 初始化方式:
- 命名字段结构体:Point {x:0, y:0}
- 元组结构体:Point2(0, 0)
- product type特性: 结构体是多个字段的组合类型,每个字段必须被初始化
2)枚举
- 基本枚举: 类似C语言的简单枚举,如
enum Direction {Left, Right, Up, Down}
- 带字段枚举: 类似tagged union,如
enum OptionalI32 {AnI32(i32), Nothing}
- sum type特性: 枚举值是多个可能类型的集合
- 初始化方式: 必须指定具体变体,如OptionalI32::AnI32(21)或OptionalI32::Nothing
- 应用场景: 特别适合处理状态相关的类型组合问题
3)泛型
- 语法形式: 在类型名后加<T>,如
struct Foo<T> {bar: T}
- 标准库示例: Option枚举就是泛型,定义为
enum Option<T> {Some(T), None}
- 实现优势: 避免为不同类型重复编写相同逻辑的代码
- 编译机制: 使用时具体化类型参数,编译时展开为具体类型版本
4)方法
- 实现语法: 使用impl块为结构体定义方法
- self参数类型:
- &self(只读): 不可变引用,方法内不能修改结构体
- &mut self(读写): 可变引用,允许修改结构体
- self(销毁): 获取所有权,调用后结构体被消耗
- 泛型方法: 实现时需要声明泛型参数,如
impl<T> Foo<T> {...}
- 所有权影响: 使用self作为参数会转移所有权,调用后原变量失效
5)Trait特质
- 概念类比: 类似Java的interface或Scala的type class
- 定义方式: 使用trait关键字定义行为规范
- 实现示例: 如FromStr特质定义了字符串解析行为
- 系统设计应用: 先定义核心数据结构,再通过特质定义它们的行为
- 组合优势: 特质可以组合多个行为,增强代码复用性
6)函数指针
- 基本概念: 指向函数而非数据的指针
- 使用场景: 主要用于高阶函数和回调机制
- 类型表示: 如
fn(i32) -> i32表示接收i32返回i32的函数指针
- 与其他语言区别: Rust的函数指针更强调类型安全性
7)模式匹配
- 语法形式: 使用match表达式进行模式匹配
- 枚举匹配: 可以匹配枚举的不同变体及其内部数据
- 深度匹配: 支持嵌套结构的多层级模式匹配
- 穷尽性检查: 编译器会强制要求处理所有可能情况
- 常见应用: 与Option和Result类型配合处理可能失败的操作
8) derive派生宏自动实现Trait
derive 的核心思想是:“让编译器去写那些无聊的样板代码”。
比如其实编译器在后台偷偷写下了:
9)宏 vs 函数
特性 | 函数 | 宏 |
调用方式 | func_name(args) | macro_name!(args) |
参数类型 | 固定类型 | 灵活,可以是任意 token |
编译时展开 | 否 | 是 |
语法检查 | 编译前 | 展开后 |
示例 | vec::new() | vec![1, 2, 3] |
有两种宏
声明式宏 (macro_rules!)
过程宏 (Procedural Macro)
控制流程
内存安全与指针
1)所有权指针
- 唯一所有权:同一时间只能有一个所有者,离开作用域时自动释放
- 所有权转移:赋值会导致所有权转移,原变量不可再用
2)引用与借用
- 不可变引用:创建引用称为"借用",借用期间原值不可修改
- 借用有效期:直到最后一次使用借用变量
3)可变引用
- 独占访问:可变借用期间,原变量完全不可访问
类型 | 含义 | 可变性 | 所有权 |
T | 拥有值 | 取决于 mut | 有 |
&T | 不可变引用 | 不可变 | 无 |
&mut T | 可变引用 | 可变 | 无 |
符号 | 含义 |
&T | 不可变引用(只读) |
&mut T | 可变引用(可读写) |
*r | 解引用(获取引用指向的值) |
类型系统

内存布局


所有权和生命周期
1. 基本规则
- 唯一拥有权:值被唯一的scope拥有,它们共存亡。scope可以是函数或花括号括起的作用域。
- 所有权转移:值可以移动到另一个scope,新的scope将拥有这个值。
- 引用规则:
- 一个值可以有多个只读引用或单个可变引用
- 这些引用之间是互斥关系(类似RwLock)
- 引用不能超越值的存活期
- 并发类比:与多线程开发中的Mutex互斥锁和读写锁原理相似,写操作需要独占访问权。
2. 值的存储方式

1)堆与栈的概述
- 栈内存:
- 动态生命周期
- 随函数调用自动增长/回收
- 存储局部变量和函数调用信息
- 堆内存:
- 未定义/受控生命周期
- 需要显式分配和释放
- 存储动态大小的数据
2)程序启动时的内存布局
- 内核空间:与内核地址映射的区域
- 程序参数区:存储程序入口参数和环境变量
- 调用栈:每个线程独立的执行调用栈
- 堆:动态分配的大内存区域
- 特殊段:
- BSS段:未初始化数据
- Read-Only Data:只读数据(如字符串字面量)
- Text段:程序代码
3)栈内存的特点与生命周期
- 自动管理:随函数调用自动分配和释放
- 确定大小:函数所需栈空间在编译期确定
- 调用过程:
- 调用时保存返回地址
- 保存寄存器副本
- 分配局部变量空间
- 压入被调用函数参数
4)堆内存的特点与管理方式
- 动态分配:大小在运行时确定
- 管理方式对比:
- C/C++:手动管理(malloc/free)
- Java/Go:GC垃圾回收
- Swift:ARC引用计数
- Rust:所有权系统自动管理
5)其他内存段
- BSS段:存储未初始化全局变量
- Read-Only Data:存储常量数据和字符串字面量
- Text段:存储程序可执行代码
6)内存空间的生命周期
- 最长生命周期:Text段、RO Data段、BSS段(与程序同生命周期)
- 动态生命周期:栈和堆内存(生命周期不确定)
7)栈与堆的引用关系
- 典型结构:栈上的胖指针指向堆内存(如String类型)
- 内存问题:
- 栈指针指向未分配堆内存 → 野指针
- 堆内存无栈引用 → 内存泄漏
8)函数调用栈变化
- 调用过程:
- 保存返回地址
- 保存寄存器副本
- 分配局部变量空间
- 压入被调用函数参数
- 调用函数
应用案例
1)String内存管理示例

- 创建过程:
- let mut greeting = String::with_capacity(32)在栈上创建胖指针
- 堆上分配32字节内存
- 写入数据:push_str("hello world")使用11字节
- 自动扩展:超过容量时会重新分配更大内存
- 释放过程:
- 调用drop()释放堆内存
- 退出作用域释放栈内存
2)悬垂指针示例

- C/C++: 程序员手动管理堆内存,容易出现内存泄漏和悬空指针。
- Java/Go: 使用垃圾回收器(GC)自动管理堆内存,但无法精确控制释放时机,存在内存累积问题。
- Rust折中方案: 通过所有权规则在编译期确保内存安全,既不需要手动管理,也避免了GC的开销。

- 潜在问题检测: 编译器能识别看似无害但可能导致内存安全问题的操作,如vector扩容导致的悬空指针。
- 错误预防: 通过所有权和借用规则,在编译期阻止可能导致内存不安全的行为。
- 实际案例:
- 当vector需要扩容时,旧内存可能被释放,导致原有引用变为悬空指针。
- Rust编译器会阻止在引用存在时修改容器的操作,从根本上避免这类问题。
- 并发安全: 所有权机制不仅解决内存安全问题,也为并发安全提供了基础保障。
- 生命周期标注: 帮助编译器理解各参数生命周期关系,确保引用有效性。
3) 所有权机制可以很好地解决内存并发安全问题

- 唯一拥有原则: 值被唯一的scope拥有,它们共存亡。例如线程一拥有字符串"hello world"时,线程二不能直接访问。
- 移动机制: 值可以移动到另一个scope,新scope将拥有这个值。如将值从线程一移动到线程二时,线程一会失去控制权。
- 引用规则:
- 一个值可以有多个只读引用或单个可变引用
- 引用之间是互斥关系(类似RwLock)
- 引用不能超过值的存活期
- 共享内存处理:
- 使用Arc等引用计数智能指针追踪访问权
- 将值移动到另一个scope(原scope会失去控制权)
- 通过Mutex或RwLock进行保护
- 内存保护机制:
- 两个scope不能同时拥有同一内存
- 需要共享时必须使用同步原语保护
- 可通过拷贝方式在不同scope间传递值
生命周期的多种情况

1)调用栈的生命周期关系
- 调用者优先原则: 调用者(main函数)的生命周期必然长于被调用者(local_ref函数)
- 非法引用示例: 不允许引用生命周期更短的变量(如local_ref函数中的变量a)
- 内存安全机制: 这种限制存在于大多数编程语言中,是内存安全的基本保障
2)堆栈生命周期一致性
- 统一生命周期模型: 将堆和栈的生命周期视为一体时,特定引用关系可以成立
- 合法引用条件:
- 堆内存的生命周期等于其引用者(data)的生命周期
- 被引用变量(v)与引用者(data)处于同一作用域
- 三者生命周期完全一致时引用关系合法
- 非法场景: 当堆内存尝试引用生命周期更短的局部变量时(如local_ref中的v),该引用关系不成立
- 核心原则: 任何内存引用必须确保被引用者的生命周期不短于引用者
传值行为

1)移动语义
- 基本概念:当值从一个scope传递到另一个scope时,默认会发生所有权转移(Move),原scope将失去对该值的所有权。
- 实现条件:适用于未实现Copy trait的数据结构,特别是包含堆内存分配的类型(如Vec<T>)。
- 内存行为:移动后,目标scope获得栈上数据的副本(仍指向相同堆内存),原scope的栈数据失效。
- 生命周期规则:值只能被唯一的scope拥有,新旧scope不能同时持有同一值的所有权。
2)复制
- Copy与Clone区别:
- Copy:仅适用于原生类型(primitive types),进行按位拷贝(浅拷贝),编译器自动实现
- Clone:可手动实现,通常需要深拷贝(如复制堆内存)
- 隐式Copy行为:对实现Copy trait的类型(如i32),传值时会自动复制
- 显式Clone:当需要保留原所有权时,需显式调用clone()
3)借用/引用
- 使用条件:当函数不需要所有权但需要访问数据时
- 语法形式:使用&符号创建引用
- 所有权保留:调用后原始scope仍保留所有权
- 生命周期约束:引用生命周期不能超过被引用值的生命周期
- 引用类型:
- 共享引用(&T):允许多个只读引用共存
- 可变引用(&mut T):独占访问,与其他引用互斥
泛型和trait
- 核心思想:泛型是数据结构的函数,通过类型参数化避免为不同数据类型重复编写相同算法代码
- 现代语言趋势:连Go语言也在近年加入泛型支持,体现其重要性
- 类比理解:如同将重复行为封装成函数,泛型是对数据结构的抽象封装

- 代码复用:
- 示例copy<R, W>函数可处理文件、socket、cache、db等多种数据源
- 避免为每种组合编写单独实现
- 抽象编程:
- 针对行为(trait约束)而非具体类型编程
- 只要实现指定trait,代码自动适配新类型
- 扩展性:未来新增数据类型只需实现相应trait即可兼容现有代码
Rust提供的trait


3. 项目开发实践
1. 代码审查最佳实践
- 架构设计
- 审查重点:新功能的架构是否符合良好设计规范
- 适用场景:对于复杂PR需要特别关注架构设计
- 接口设计
- 审查范围:包括API、数据结构和公共方法
- 关键指标:接口设计的合理性是代码质量的核心要素
- 代码质量
- 最佳实践:
- 命名规范性
- 算法效率(如避免不必要的循环)
- 可读性:避免晦涩难懂的算法,必要时需注明优化原因
- 安全性:
- 数据库操作需防范注入风险
- 前端代码需防范CSRF和XSS攻击
- 错误处理
- 重要性:错误处理与正确路径同等重要
- 设计原则:
- 错误处理接口应合理
- 处理范围要适度,不宜过大或过细
- 可维护性
- DRY原则:杜绝代码复制行为
- KISS原则:保持代码简单
- 文档要求:在关键位置提供必要但不冗余的文档
- 团队规范
- 审查依据:需符合团队特定实践要求
- 自动化检查:格式检查等基础工作应交由CI完成
2. 合并流程规范
- 分支保护:
- 主分支应设置保护
- 防止意外修改
- PR合并条件:所有检查通过后才能合并
- 合并方式偏好:推荐使用squash merge将PR内所有提交合并为一个
- 分支清理:合并后应及时删除特性分支