简明Rust语法介绍
Rust 是一门注重内存安全、性能和并发性的系统编程语言。以下是其核心语法的扩展介绍,涵盖更多细节和实用特性,适合初学者快速了解并深入掌握。
1. 变量与常量
变量:使用
let声明,默认不可变。使用mut允许修改。变量可以被遮蔽(重新声明)。let x = 5; let x = x + 1; // 遮蔽,创建新变量 let mut y = 10; y = 15;常见场景与注意事项:
- 配置常量与全局值:不可变配置信息用
const或static,但如果需要延迟初始化或线程安全的可变全局,优先使用once_cell::sync::Lazy或lazy_static+Mutex。 - 遮蔽(shadowing)用于按步骤转换值(例如
let s = s.trim();),可以减少可变性并限定作用域。 - 谨慎使用
static mut—— 这是不安全的;如需全局可变状态请使用Atomic*类型或加锁封装。 - 类型推断与转换:Rust 编译器能自动推断大多数类型,但在复杂情况下需要显式声明。
let x = 5; // 推断为 i32 let y: f64 = 3.14; // 显式声明类型 let z = x as f64; // 类型转换
- 配置常量与全局值:不可变配置信息用
常量:使用
const声明,需指定类型,全局作用域,值在编译时确定。const MAX_VALUE: u32 = 100_000; // 下划线提高数字可读性常见场景与注意事项:
const适用于编译期已知、不依赖运行时环境的常量(如默认缓冲大小、协议常量)。- 如果需要运行时初始化的全局不可变值,使用
once_cell::sync::Lazy初始化static。
静态变量:使用
static声明,生命周期贯穿程序运行。static GREETING: &str = "Hello, Rust!";常见场景与注意事项:
static保存静态字符串、表驱动数据或与 FFI 交互时的固定地址数据。- 若需要可变静态数据,请使用
static搭配线程安全原语(如Atomic*、Mutex)或Lazy延迟初始化。 - 类型别名:使用
type关键字创建类型别名,提高代码可读性。type Kilometers = i32; type Result<T> = std::result::Result<T, String>; - 字面量后缀:使用后缀明确指定数值类型。
let x = 42u8; // u8 类型 let y = 3.14f32; // f32 类型 let z = 100_000i64; // i64 类型,使用下划线提高可读性
2. 数据类型
- 标量类型:
- 整数:有符号 (
i8,i16,i32,i64,i128),无符号 (u8,u16,u32,u64,u128),以及isize/usize(与系统架构相关)。 - 浮点数:
f32,f64。 - 布尔:
bool(true,false)。 - 字符:
char(Unicode,单引号,如'😊')。
- 整数:有符号 (
- 复合类型:
- 元组:固定长度,异构类型,可通过
.0,.1访问或解构。let tup: (i32, f64, char) = (500, 6.4, 'z'); let (x, y, z) = tup; // 解构 - 数组:固定长度,同构类型,栈分配。
let arr: [i32; 5] = [1, 2, 3, 4, 5]; let first = arr[0]; - 向量:动态数组,使用标准库
Vec<T>,堆分配。let mut v = vec![1, 2, 3]; v.push(4); - 切片(Slice):对连续序列的“视图”,不拥有数据,长度在运行时确定。常用于字符串与数组的局部访问,避免拷贝。
let arr = [1, 2, 3, 4, 5]; let slice: &[i32] = &arr[1..4]; // 包含 1..3,即 [2, 3, 4] println!("{:?}", slice); // [2, 3, 4] let s = String::from("hello rust"); let hello: &str = &s[0..5]; // 字符串切片 println!("{}", hello); // hello- 切片类型写作
&[T]或&str,自带长度信息,可与泛型配合写出通用算法。 - 空切片
&[]合法,可用于表示“无数据”而无需Option。 - 切片与借用规则一致:同一时刻只能有一个可变切片或多个不可变切片。
- 切片类型写作
- 元组:固定长度,异构类型,可通过
常见场景与注意事项:
- 字符串边界:处理 UTF-8 字符串时不要盲目按字节切片(
&s[0..n]),这可能截断多字节字符;使用char迭代或s.get(..n)做安全检查。 - 数组 vs Vec:如果长度固定且小且无需堆分配,优先用数组;若涉及动态增长或可变长度,使用
Vec<T>并考虑with_capacity以减少重分配。 - API 设计:函数接受
&[T]或&str更通用,可以接收数组、切片和向量的引用,减少拷贝。 - 字符串类型:
String:可增长的 UTF-8 字符串,拥有数据。&str:字符串切片,不拥有数据。
let s1 = String::from("hello"); let s2 = "world"; // &str 类型 let s3 = s1 + " " + s2; // String 拼接 let len = s3.len(); // 获取字节长度(非字符数) - 类型转换:
let num_str = "42"; let num: i32 = num_str.parse().unwrap(); // 字符串转数字 let str_from_num = num.to_string(); // 数字转字符串
3. 函数
- 函数使用
fn定义,参数和返回值需显式指定类型。无返回值时省略->。fn add(a: i32, b: i32) -> i32 { a + b // 表达式返回值,无需 return } - 发散函数:永不返回,使用
!作为返回类型(如panic!)。fn diverge() -> ! { panic!("This function never returns!"); }
常见场景与注意事项:
- 小函数优先:把复杂逻辑拆成小函数,提高可测试性与可读性,便于单元测试。
- 错误传播与返回类型:库函数应尽量返回
Result<T, E>,避免在库内部panic!,将不可恢复错误留在应用层或测试。 - 函数签名设计:如果函数需要灵活接受所有权或引用,提供多种签名(例如接收
&str而非String),或写成泛型以适配更多输入。 - 函数指针:可以将函数作为参数传递或存储在变量中。
fn add(a: i32, b: i32) -> i32 { a + b } let operation: fn(i32, i32) -> i32 = add; let result = operation(5, 3); - 高阶函数:接受函数作为参数或返回函数的函数。
fn apply_twice<F>(f: F, x: i32) -> i32 where F: Fn(i32) -> i32 { f(f(x)) } let result = apply_twice(|x| x + 1, 5); // 7 - 返回语句:虽然 Rust 是表达式语言,但也可以使用显式返回。
fn early_return(x: i32) -> i32 { if x > 10 { return x * 2; // 提前返回 } x + 1 // 隐式返回 }
4. 控制流
- 条件语句:
if支持表达式风格返回值,无需三元运算符。let number = 7; let result = if number > 0 { "positive" } else { "non-positive" };- 条件必须是
bool类型。
- 循环:
loop:无限循环,可用break返回值。let mut counter = 0; let result = loop { counter += 1; if counter == 10 { break counter * 2; } };while:条件循环。while counter < 15 { counter += 1; }for:常用于迭代器或范围。for i in 0..5 { // 包含 0,不包含 5 println!("i: {}", i); }
常见场景与注意事项:
- 使用
if表达式构造值可以避免可变变量,比如let status = if cond { a } else { b };。 - 选择合适的迭代方式:
iter()(借用)、iter_mut()(可变借用)、into_iter()(消费并取得所有权),以避免所有权相关编译错误。 loop+break值在需要复杂退出条件且最终返回计算结果时更清晰。- 模式匹配在控制流中的应用:
let pair = (0, -2); match pair { (0, y) => println!("x=0, y={}", y), (x, 0) => println!("x={}, y=0", x), _ => println!("其他情况"), } - 标签循环:可以给循环添加标签,用于明确指定 break/continue 的目标。
'outer: for i in 0..3 { 'inner: for j in 0..3 { if i == 1 && j == 1 { break 'outer; } println!("({}, {})", i, j); } }
5. 所有权与借用
- 所有权:每个值有唯一所有者,离开作用域时释放。移动后原变量失效。
let s1 = String::from("hello"); let s2 = s1; // s1 失效 // println!("{}", s1); // 错误:s1 已移动 - 借用:使用
&创建不可变引用,&mut创建可变引用。同一时间只能有一个可变引用或多个不可变引用。let mut s = String::from("hello"); let r1 = &s; // 不可变借用 // let r2 = &mut s; // 错误:不可同时有可变和不可变借用 - 复制:实现了
Copytrait 的类型(如基本类型)不会移动。let x = 5; let y = x; // 复制,x 仍有效
常见场景与注意事项:
- 函数参数设计:若函数不需要修改数据,使用
&T;若需要修改则&mut T;若函数需要获得所有权(例如构建返回值),传T。 - Clone 的成本:对大型结构频繁
clone()会有性能开销,优先考虑借用或使用共享引用(Rc/Arc)。 - 临时值借用:不要返回对局部
String的&str—— 这会导致借用指向超出作用域的值(编译器错误)。 - 常见陷阱:在同一作用域内同时持有不可变借用与可变借用(例如在遍历同时修改容器)会被编译器拒绝。
- 借用检查器规则总结:
- 规则1:任何借用必须在其引用的值的生命周期内有效。
- 规则2:可以同时存在多个不可变借用 (
&T)。 - 规则3:不能同时存在可变借用 (
&mut T) 和任何其他借用。
let mut s = String::from("hello"); let r1 = &s; // 不可变借用 let r2 = &s; // 另一个不可变借用 - 允许 // let r3 = &mut s; // 错误:不能同时有可变借用 println!("{} {}", r1, r2); // r1 和 r2 在这里不再使用 let r3 = &mut s; // 现在允许 - 之前的借用已结束
6. 结构体与枚举
- 结构体:
- 命名结构体:
struct Rectangle { width: u32, height: u32, } let rect = Rectangle { width: 30, height: 50 }; - 元组结构体:
struct Color(u8, u8, u8); let black = Color(0, 0, 0); - 方法:使用
impl定义。impl Rectangle { fn area(&self) -> u32 { self.width * self.height } }
- 命名结构体:
- 枚举:支持复杂数据结构,常与
match结合。enum Message { Quit, Move { x: i32, y: i32 }, Write(String), }
常见场景与注意事项:
- 枚举用于状态机建模(例如连接状态、任务状态),结合
match可保证覆盖所有分支。 - DTO 与业务实体:接口层返回轻量 DTO,避免将内部可变状态或锁直接暴露给调用者。
- impl 方法签名:不可变操作使用
&self,需要修改使用&mut self,并根据可能的错误返回Result。 - 关联函数(不取 self 参数):
impl Rectangle { fn square(size: u32) -> Self { Self { width: size, height: size } } } let sq = Rectangle::square(10); - 枚举的常见模式:
enum IpAddr { V4(u8, u8, u8, u8), V6(String), } let home = IpAddr::V4(127, 0, 0, 1); let loopback = IpAddr::V6(String::from("::1"));
7. 模式匹配
- match:穷尽匹配,必须覆盖所有情况。
let num = Some(4); match num { Some(x) if x % 2 == 0 => println!("Even: {}", x), Some(x) => println!("Odd: {}", x), None => println!("None"), } - if let:简化单一模式匹配。
if let Some(x) = num { println!("Value: {}", x); }
常见场景与注意事项:
- 使用
if let和while let处理单一分支或逐项解析,代码更简洁。 - 在匹配复杂结构时使用解构(
{ x, y, .. }或(_, _, tail)),并用_/..忽略不必要字段。 - 当有默认分支时尽量显式写出
_ => {},以便未来扩展时不遗漏处理。 - 守卫条件(match guards):
let num = Some(4); match num { Some(x) if x < 5 => println!("小于5: {}", x), Some(x) => println!("大于等于5: {}", x), None => println!("没有值"), } - 绑定模式:
let value = Some(10); if let Some(x) = value { println!("值是: {}", x); } else { println!("没有值"); } - while let 循环:
let mut stack = vec![1, 2, 3]; while let Some(top) = stack.pop() { println!("弹出: {}", top); }
8. 错误处理
- Option:处理可能为空的值 (
Some(T)或None)。let opt: Option<i32> = Some(5); let value = opt.unwrap_or(0); // 解包或提供默认值 - Result:处理可能出错的操作 (
Ok(T)或Err(E))。fn divide(a: i32, b: i32) -> Result<i32, String> { if b == 0 { Err(String::from("Divide by zero")) } else { Ok(a / b) } } let result = divide(10, 2).expect("Division failed"); // 成功返回 5 - ? 运算符:简化错误传播。
fn try_divide() -> Result<i32, String> { let result = divide(10, 0)?; Ok(result) }
常见场景与注意事项:
- 传播错误:使用
?链式传播并在上层提供上下文(结合thiserror/anyhow/eyre等库)。 - 错误类型设计:库内使用精确错误类型(
enum Error),应用层可以用anyhow::Error简化处理。 - Option vs Result:仅”可能不存在”用
Option,若需错误信息使用Result。 - 使用
map_err,with_context来附带额外上下文,便于排查问题。 - 组合子方法:
let opt1: Option<i32> = Some(5); let opt2: Option<i32> = Some(10); let sum = opt1.and_then(|x| opt2.map(|y| x + y)); // Some(15) let result: Result<i32, &str> = Ok(5); let doubled = result.map(|x| x * 2); // Ok(10) - 错误类型转换:
fn parse_number(s: &str) -> Result<i32, std::num::ParseIntError> { s.parse::<i32>() } fn double_number(s: &str) -> Result<i32, String> { parse_number(s) .map(|n| n * 2) .map_err(|e| format!("解析失败: {}", e)) }
9. 模块系统
- 使用
mod定义模块,pub控制可见性,use引入路径。mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } } use crate::front_of_house::hosting; hosting::add_to_waitlist(); - 文件系统模块:
mod声明会加载同名文件或目录中的代码。
常见场景与注意事项:
- 目录布局:在库 crate 中把公共 API 暴露在
lib.rs顶层,内部实现放子模块并只pub出必要接口。 - re-export:使用
pub use聚合导出,简化外部调用路径(例如pub use crate::module::Type)。 - 避免循环依赖:通过拆分模块、提取 trait 或把共享接口移动到顶层模块来解决。
- 模块路径系统:
// 绝对路径 crate::front_of_house::hosting::add_to_waitlist(); // 相对路径 front_of_house::hosting::add_to_waitlist(); // 使用 super 访问父模块 super::some_function(); - 使用 as 关键字重命名:
use std::collections::HashMap as Map; let mut map = Map::new(); - 通配符导入(谨慎使用):
use std::collections::*;
10. 集合类型
- Vec:动态数组。
let mut v = Vec::new(); v.push(1); - HashMap:键值对集合。
use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10);
常见场景与注意事项:
- Vec 性能:当已知大小时使用
Vec::with_capacity(n)预分配以避免重复分配。 - HashMap 常用技巧:使用
entryAPI(or_insert,or_insert_with)减少查找与初始化重复。 - 并发集合:在多线程共享时可用
Arc<Mutex<HashMap<...>>>或使用dashmap等并发数据结构减少锁竞争。 - 其他常用集合:
use std::collections::{HashSet, BTreeMap, BTreeSet, VecDeque, BinaryHeap}; // HashSet - 无序集合 let mut set = HashSet::new(); set.insert(1); set.insert(2); // VecDeque - 双端队列 let mut deque = VecDeque::new(); deque.push_front(1); deque.push_back(2); // BinaryHeap - 二叉堆(优先队列) let mut heap = BinaryHeap::new(); heap.push(3); heap.push(1); heap.push(2); println!("{}", heap.peek().unwrap()); // 3 - 迭代器适配器:
let v = vec![1, 2, 3, 4, 5]; let doubled: Vec<i32> = v.iter().map(|x| x * 2).collect(); let evens: Vec<&i32> = v.iter().filter(|x| *x % 2 == 0).collect();
11. 泛型与 trait
- 泛型:提高代码复用性。
fn largest<T: PartialOrd>(list: &[T]) -> &T { &list[0] } - trait:定义共享行为,类似接口。
trait Summary { fn summarize(&self) -> String; } struct Article { content: String, } impl Summary for Article { fn summarize(&self) -> String { format!("Article: {}", self.content) } }
常见场景与注意事项:
- 泛型约束:当约束复杂时使用
where语句提高可读性。 - Trait 对象:需要在运行时动态分派时使用
Box<dyn Trait>,注意 object-safe 规则(某些 trait 无法作为 trait 对象)。 - 为通用库设计泛型接口时小心二次编译膨胀(code bloat),并考虑为常见类型提供专门实现。
- 关联类型:
trait Container { type Item; fn get(&self, index: usize) -> Self::Item; } struct VecContainer<T> { items: Vec<T>, } impl<T: Clone> Container for VecContainer<T> { type Item = T; fn get(&self, index: usize) -> Self::Item { self.items[index].clone() } } - 默认实现:
trait Summary { fn summarize(&self) -> String { String::from("(Read more...)") } }
12. 生命周期
- 确保引用有效,生命周期注解用
'a表示。fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }
常见场景与注意事项:
- 不要返回对局部变量的引用(会导致编译错误)。
- 当生命周期签名复杂时,考虑返回拥有所有权的类型(如
String)或使用Arc/Cow来简化 API。 - 在接口设计中尽量让调用方持有所有权或明确借用关系,减少不必要的生命周期注解暴露。
- 生命周期省略规则(Lifetime Elision):
Rust 编译器会自动应用三条规则来省略生命周期注解:- 每个引用参数都有自己的生命周期参数
- 如果只有一个输入生命周期参数,它被赋予所有输出生命周期参数
- 如果有多个输入生命周期参数,但其中一个时
&self或&mut self,那么self的生命周期被赋予所有输出生命周期参数
// 编译器可以自动推断,无需显式生命周期注解 fn first_word(s: &str) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..] } - 静态生命周期:
let s: &'static str = "I have a static lifetime.";
13. 并发
- 线程:使用
std::thread。use std::thread; let handle = thread::spawn(|| { println!("Running in a thread!"); }); handle.join().unwrap(); - 消息传递:通过通道 (
mpsc)。use std::sync::mpsc; let (tx, rx) = mpsc::channel(); tx.send(42).unwrap(); println!("Received: {}", rx.recv().unwrap()); - 共享状态:使用
Mutex和Arc.use std::sync::{Arc, Mutex}; let counter = Arc::new(Mutex::new(0));
常见场景与注意事项:
- 任务划分:IO 密集型使用异步模型(tokio),CPU 密集型使用线程池或
spawn_blocking。 - 共享计数器:若只是计数,优先使用原子类型(
AtomicUsize)以减少锁开销。 - 通道模式:
mpsc适合生产者-消费者;对性能敏感场景可用crossbeam提供更高性能的通道。 - 避免死锁:尽量缩小锁的持有范围,并避免在持锁期间调用外部代码或再加锁。
- 读写锁(RwLock):
use std::sync::RwLock; let lock = RwLock::new(5); // 多个读者 { let r1 = lock.read().unwrap(); let r2 = lock.read().unwrap(); println!("{}", *r1); } // 读锁在这里释放 // 单个写者 { let mut w = lock.write().unwrap(); *w += 1; } - 条件变量(Condvar):
use std::sync::{Arc, Mutex, Condvar}; let pair = Arc::new((Mutex::new(false), Condvar::new())); let pair2 = Arc::clone(&pair); std::thread::spawn(move || { let (lock, cvar) = &*pair2; let mut started = lock.lock().unwrap(); *started = true; cvar.notify_one(); }); let (lock, cvar) = &*pair; let mut started = lock.lock().unwrap(); while !*started { started = cvar.wait(started).unwrap(); }
14. 常用宏
println!,format!:格式化输出。vec!:快速创建向量。panic!:触发程序崩溃。todo!:标记未实现代码。let v = vec![1, 2, 3]; println!("{:?}", v);
常见场景与注意事项:
- 调试:使用
dbg!(expr)快速打印表达式及其位置,便于排查。 - 宏滥用:宏能生成重复代码,但会增加调试难度;如果函数或 trait 已能满足需求,优先使用函数/trait。
- 自定义宏:在需要元编程或减少重复模板化代码(如注册、绑定)时使用,但注意错误消息友好性。
- 格式化宏详解:
let name = "Rust"; let version = 1.70; println!("语言: {}, 版本: {}", name, version); // 基本格式化 println!("二进制: {:b}", 10); // 1010 println!("十六进制: {:x}", 255); // ff println!("指针: {:p}", &name); // 内存地址 println!("调试: {:?}", (1, 2, 3)); // 调试格式 - 断言宏:
assert!(true); assert_eq!(2 + 2, 4); assert_ne!(1, 2); debug_assert!(some_condition); // 仅在 debug 模式下检查
15. 智能指针
Rust 中的“智能指针”不仅仅是指针,它们还可以提供额外的所有权语义与运行时行为。常见的智能指针包括 Box<T>、Rc<T>、Arc<T>、Cell<T> 与 RefCell<T>。下面分别介绍它们的用途、约束与示例。
Box
:将值放到堆上,最简单的智能指针。常用于: - 在编译时大小不确定但需要在堆上存储(如递归数据结构)。
- 明确将大对象移到堆以减小栈开销。
let b = Box::new(5); let boxed_array: Box<[i32]> = Box::new([1, 2, 3]);常见场景与注意事项:
- 用于实现递归数据结构(例如自定义链表和树),把子节点放在
Box中以满足大小已知要求。 - 在 API 需要明确传递堆上数据的所有权时使用
Box<T>。
- 用于实现递归数据结构(例如自定义链表和树),把子节点放在
Rc
(Reference Counted) :引用计数智能指针,适用于单线程场景,允许多个所有者共享同一块数据。Rc<T>在克隆时会增加引用计数,直到所有者都被丢弃,数据才会被释放。注意:Rc<T>不能用于跨线程共享(不是 Send/Sync)。- 使用场景:树结构、图结构或需要多个所有者但只在单线程中的共享数据。
use std::rc::Rc; let a = Rc::new(String::from("hello")); let b = Rc::clone(&a); // 增加引用计数 println!("count = {}", Rc::strong_count(&a)); // 2常见场景与注意事项:
- 与
RefCell<T>搭配使用:Rc<RefCell<T>>常用于单线程内既需多所有者又需内部可变性的结构(如可变树)。 - 注意循环引用会导致内存泄漏;若需要父指针请使用
Weak断开循环。
- 与
Arc
(Atomic Reference Counted) :与Rc<T>类似,但使用原子操作维护引用计数,因此可以安全地在多线程间共享(实现了Send与Sync,前提是 T 本身满足相应约束)。相比Rc,Arc的计数开销更高,但可用于并发场景。use std::sync::Arc; use std::thread; let data = Arc::new(vec![1, 2, 3]); let mut handles = Vec::new(); for _ in 0..3 { let d = Arc::clone(&data); handles.push(thread::spawn(move || { println!("{:?}", d); })); } for h in handles { h.join().unwrap(); }常见场景与注意事项:
- 在多线程共享读/写场景中通常与
Mutex或RwLock结合(Arc<Mutex<T>>、Arc<RwLock<T>>)。 - 当仅需要只读共享时,优先使用
Arc<T>(且 T 为不可变或只读)以减少锁开销。
- 在多线程共享读/写场景中通常与
Cell
与 RefCell :它们允许在外部不可变绑定下对内部数据进行修改(绕开编译时的借用规则,但在运行时仍保证安全性)。(内部可变性 / Interior Mutability) Cell<T>:提供按值存取(Copy 或可移动类型),常用方法有get/set/replace。不提供引用借用;适合小型标量或实现缓存的场景。use std::cell::Cell; let c = Cell::new(5); c.set(10); let v = c.get();RefCell<T>:在单线程中用于在运行时进行借用检查。通过borrow()(不可变借用,返回Ref<T>)和borrow_mut()(可变借用,返回RefMut<T>)实现。当出现违反借用规则的情况(例如同时存在一个borrow()和一个borrow_mut()),会在运行时 panic。- 常见搭配:
Rc<RefCell<T>>,用于在单线程中既需要多个所有者(Rc)又需要在任意时间点可变访问(RefCell)的场景,例如构建可变的树/图。
use std::rc::Rc; use std::cell::RefCell; #[derive(Debug)] struct Node { value: i32, children: Vec<Rc<RefCell<Node>>>, } let leaf = Rc::new(RefCell::new(Node { value: 3, children: vec![] })); let branch = Rc::new(RefCell::new(Node { value: 5, children: vec![Rc::clone(&leaf)] })); // 修改 leaf 的值,即使它被多个 Rc 所拥有 leaf.borrow_mut().value = 10; println!("{:?}", branch);- 常见搭配:
设计和使用要点(实践建议):
- 优先使用编译时借用规则(& / &mut)。只有在确实需要多个所有者或内部可变性时,才使用智能指针。
- 使用
Rc<T>时注意循环引用(reference cycle)会导致内存泄漏,因为引用计数永远无法降为 0。若确实需要父子双向引用,可用Weak<T>(弱引用)来打破循环:use std::rc::{Rc, Weak}; use std::cell::RefCell; struct Node { parent: RefCell<Weak<Node>>, children: RefCell<Vec<Rc<Node>>> } RefCell<T>在运行时检查借用,错误会导致 panic。若需要在运行时避免 panic,应在逻辑上保证借用规则或使用更合适的并发原语(如Mutex)。- 在多线程场景下,使用
Arc<T>+Mutex<T>或Arc<RwLock<T>>来实现线程安全的可变共享数据,而不是Rc/RefCell。 - Cow(写时克隆):
use std::borrow::Cow; fn remove_spaces<'a>(input: &'a str) -> Cow<'a, str> { if input.contains(' ') { Cow::Owned(input.replace(' ', "")) } else { Cow::Borrowed(input) } } - Pin 和 Unpin(高级特性):
用于确保某些类型在内存中的位置不会被移动,主要用于异步编程。use std::pin::Pin; let mut val = 5; let pinned = Pin::new(&mut val);
小结:
- Box:将值放到堆上(所有权明确)。
- Rc:单线程引用计数,多所有者(只读或配合内部可变性)。
- Arc:跨线程的原子引用计数。
- Cell / RefCell:在单线程下提供“内部可变性”,在运行时检查借用规则(RefCell 会 panic 当规则被违反)。
- Weak:与 Rc/Arc 配合使用,避免循环引用,不能增加引用计数但可以升级为强引用(若仍存活)。
16. 迭代器与闭包
- 迭代器(Iterator):Rust 的
for循环本质就是迭代器语法糖。任何实现Iterator的类型都可以用for遍历。let v = vec![1, 2, 3]; let mut iter = v.iter(); // 产生 &i32 assert_eq!(iter.next(), Some(&1));- 常用适配器:
map、filter、collect、fold、enumerate、zip等,均返回新迭代器,实现“零成本抽象”。
let sum: i32 = (1..=10).filter(|&x| x % 2 == 0).sum();- 自定义迭代器:实现
Iterator关联类型Item与next方法即可。
- 常用适配器:
- 闭包(Closure):匿名函数,可捕获环境变量,语法
|param| body。let factor = 2; let multiply = |x| x * factor; assert_eq!(multiply(5), 10);- 闭包会根据捕获方式自动实现
Fn、FnMut或FnOnce三种 trait,决定可被调用多次/可变/一次性。 - 常与迭代器搭配,实现链式数据处理:
let v: Vec<i32> = vec![1, 2, 3] .into_iter() .map(|x| x * x) .collect(); - 闭包会根据捕获方式自动实现
常见场景与注意事项:
- 流式处理:用迭代器链实现数据转换流水线,既简洁又高效(惰性求值)。
- 惰性计算:迭代器直到被消费(如
collect、sum、for)才执行,适合处理大数据/流式数据。 - 捕获语义:闭包默认按需借用或移动捕获变量;遇到所有权冲突时可使用
move强制移动捕获。 - 迭代器消费者:
let v = vec![1, 2, 3, 4, 5]; // 消费迭代器并产生结果 let sum: i32 = v.iter().sum(); let product: i32 = v.iter().product(); let max: Option<&i32> = v.iter().max(); let min: Option<&i32> = v.iter().min(); // 检查条件 let all_positive = v.iter().all(|&x| x > 0); let any_large = v.iter().any(|&x| x > 3); - 闭包 trait 详解:
Fn:可以多次调用,不获取所有权FnMut:可以多次调用,可能修改环境FnOnce:只能调用一次,获取所有权
fn apply<F>(f: F) -> i32 where F: Fn(i32) -> i32 { f(5) } let x = 10; let add_x = |y| y + x; // 实现 Fn println!("{}", apply(add_x)); // 15
17. 属性与条件编译
- 属性(Attribute):以
#[...]形式给编译器提供元数据。#[derive(Debug, Clone)] struct Point { x: f64, y: f64 } #[inline(always)] fn fast_add(a: i32, b: i32) -> i32 { a + b } - 条件编译:使用
#[cfg(...)]按平台/特性包含或排除代码。#[cfg(target_os = "linux")] fn on_linux() { println!("Running on Linux"); } #[cfg(feature = "serde")] impl serde::Serialize for Config { /* ... */ }- 在
Cargo.toml中通过[features]定义可选特性,实现“按需编译”。
- 在
常见场景与注意事项:
- 平台相关分支:用
#[cfg(...)]为不同平台实现差异化逻辑,保持同一 API。 - 可选依赖:在
Cargo.toml使用 feature 将可选功能隔离,减少默认依赖体积(例如serde)。 - 测试特定代码:用
#[cfg(test)]隔离测试辅助代码,避免污染生产可执行文件。 - 自定义属性:
// 自定义条件编译属性 #[cfg_attr(feature = "nightly", feature(specialization))] fn specialized_function() {} // 标记废弃 #[deprecated(since = "1.0.0", note = "请使用 new_function 代替")] fn old_function() {} // 允许特定警告 #[allow(dead_code)] fn unused_function() {} - 内联属性详解:
#[inline] // 建议内联 #[inline(always)] // 总是内联 #[inline(never)] // 从不内联 fn example() {}
18. 异步编程
- async/.await:Rust 使用零成本抽象的异步模型,基于
Futuretrait。use tokio; // 常用运行时 #[tokio::main] async fn main() { let task = async { 42 }; let val = task.await; println!("{}", val); }async fn返回实现Future的匿名类型;.await挂起当前任务,让出线程。- 并发原语:
join!(并发等待多个 Future)、select!(多路复用)、spawn(任务调度)。 - 注意:异步块默认单线程调度,CPU 密集任务应使用
spawn_blocking或专用线程池。
常见场景与注意事项:
- 运行时选择:在应用层使用 tokio 或 async-std;库层尽量不直接依赖特定运行时以提高可组合性(使用
futures兼容 trait)。 - IO 密集型服务:HTTP、数据库访问、网络爬虫等使用 async 可以获得高并发吞吐。
- 阻塞操作:避免在 async 任务中直接执行阻塞 IO 或 CPU 密集任务,使用
spawn_blocking或单独线程池。 - Future trait 基础:
use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; struct MyFuture { count: u32, } impl Future for MyFuture { type Output = u32; fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> { if self.count > 0 { self.count -= 1; Poll::Pending } else { Poll::Ready(42) } } } - 异步 trait(实验性):
#[async_trait::async_trait] // 需要 async-trait crate trait AsyncService { async fn process(&self, data: &str) -> String; }
19. 宏系统
- 声明宏(macro_rules!):基于模式匹配生成代码。
macro_rules! say_hello { () => { println!("Hello!"); }; ($name:expr) => { println!("Hello, {}!", $name); }; } say_hello!(); say_hello!("Rust"); - 过程宏(Procedural Macro):分为
#[derive(...)]、#[attribute]和#[function_like]三种,通过proc_macrocrate 实现,用于代码生成、DSL 等高级场景。- 示例:自定义
#[derive(MyTrait)]为结构体自动生成实现。
- 示例:自定义
常见场景与注意事项:
- 使用
derive为常见 trait(Debug,Clone,Serialize,Deserialize)自动生成实现,减少模板代码。 - 过程宏适合生成重复模板代码或实现 DSL,但编写与调试更复杂,务必提供清晰的错误信息与文档。
- 宏会使错误信息与展开代码更难调试,原则上优先用函数、trait、泛型实现可行的逻辑。
- 过程宏示例:
// derive 宏 #[derive(Debug, Clone, PartialEq)] struct Point { x: i32, y: i32 } // attribute 宏 #[tokio::main] async fn main() {} // function-like 宏 let v = vec![1, 2, 3]; // vec! 是声明宏 - 宏的卫生性(Hygiene):
Rust 宏是卫生的,意味着它们不会意外捕获外部作用域的标识符。macro_rules! make_function { () => { fn helper() -> i32 { 42 } }; } make_function!(); // helper(); // 错误:helper 不在作用域中
20. 工具链与生态
- Cargo:Rust 的构建系统与包管理器。
cargo new、build、run、test、check、clippy、fmt等常用命令。Cargo.toml声明依赖、特性、工作区;Cargo.lock锁定版本,确保可重复构建。
- crates.io:官方包仓库,发布与获取开源库。
- rustup:工具链管理器,支持安装/切换 stable/beta/nightly 版本,以及交叉编译目标。
- 常用开发工具:
rustfmt:自动格式化代码。clippy:Lint 工具,提供 500+ 规则检查。rust-analyzer:IDE 语言服务器,支持 VS Code、Vim、Emacs 等。
- 测试体系:
- 单元测试:与源码同文件,置于
#[cfg(test)] mod tests。 - 集成测试:放在
tests/目录,独立可执行。 - 文档测试:在
///注释中写```代码块,cargo test自动运行。
- 单元测试:与源码同文件,置于
- 文档生成:
cargo doc --open生成并打开 HTML 文档,支持 Markdown 与交叉链接。 - 性能调优:
cargo bench:使用#[bench]进行基准测试(需 nightly)。perf/flamegraph:采样分析;cargo profiler集成火焰图。#[inline]、#[cold]、std::hint::unreachable_unchecked()等微优化提示。
- 安全与审计:
cargo audit:检查依赖已知漏洞。cargo geiger:统计 unsafe 代码占比。cargo deny:强制许可证/漏洞/重复依赖策略。
常见场景与注意事项:
- CI 集成:在 CI 中强制运行
cargo fmt -- --check、cargo clippy -- -D warnings、cargo test保持代码质量。 - 依赖审计:使用
cargo audit定期扫描依赖漏洞,尤其在生产部署前。 - 本地开发:使用
rustup管理工具链并在项目中记录所需的 toolchain(rust-toolchain.toml),保证团队一致性。 - 性能剖析:在性能热点使用采样工具(perf + flamegraph)而非盲目微优化;仅在确认热点后才用
#[inline]等属性。 - 工作空间(Workspace):
# Cargo.toml [workspace] members = [ "crate-a", "crate-b", ] - 发布配置优化:
# Cargo.toml [profile.release] lto = true # 启用链接时优化 codegen-units = 1 # 单代码生成单元 panic = "abort" # 移除 panic 处理代码 - 常用开发命令:
cargo tree # 查看依赖树 cargo expand # 查看宏展开结果 cargo metadata # 获取项目元数据 cargo fix # 自动修复编译警告 cargo miri # 运行 Miri 检测未定义行为 - 交叉编译:
# 安装目标平台 rustup target add x86_64-unknown-linux-musl # 为特定目标构建 cargo build --target x86_64-unknown-linux-musl - 自定义构建脚本:
// build.rs fn main() { // 告诉 Cargo 在重新运行构建脚本时,如果 src/hello.c 改变则重新运行 println!("cargo:rerun-if-changed=src/hello.c"); // 使用 cc crate 编译 C 代码 cc::Build::new() .file("src/hello.c") .compile("hello"); } - 环境变量:
// 在编译时获取环境变量 const VERSION: &str = env!("CARGO_PKG_VERSION"); const NAME: &str = env!("CARGO_PKG_NAME"); // 在运行时获取 if let Ok(path) = std::env::var("PATH") { println!("PATH: {}", path); }



