简明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); }- Range(范围)语法:Rust 提供了多种范围表达式,用于创建序列和切片。
- 半开区间
start..end:包含 start,不包含 endfor i in 1..5 { // 1, 2, 3, 4 println!("{}", i); } - 闭区间
start..=end:包含 start 和 endfor i in 1..=5 { // 1, 2, 3, 4, 5 println!("{}", i); } - 从起点开始
..end:从默认起点到 end(不包含)let arr = [10, 20, 30, 40, 50]; let slice = &arr[..3]; // [10, 20, 30] - 到终点结束
start..:从 start 到终点(包含)let arr = [10, 20, 30, 40, 50]; let slice = &arr[2..]; // [30, 40, 50] - 全范围
..:包含所有元素let arr = [10, 20, 30, 40, 50]; let slice = &arr[..]; // [10, 20, 30, 40, 50] - Range 类型:
Range<T>:start..endRangeInclusive<T>:start..=endRangeFrom<T>:start..RangeTo<T>:..endRangeToInclusive<T>:..=endRangeFull:..
use std::ops::{Range, RangeInclusive, RangeFrom, RangeTo, RangeToInclusive, RangeFull}; let r1: Range<i32> = 1..5; let r2: RangeInclusive<i32> = 1..=5; let r3: RangeFrom<i32> = 3..; let r4: RangeTo<i32> = ..5; let r5: RangeToInclusive<i32> = ..=5; let r6: RangeFull = ..;- Range 迭代:Range 实现了
Iteratortrait,可以直接用于循环// 倒序迭代 for i in (1..=5).rev() { println!("{}", i); // 5, 4, 3, 2, 1 } // 步长迭代(使用 step_by) for i in (0..10).step_by(2) { println!("{}", i); // 0, 2, 4, 6, 8 }
- 半开区间
常见场景与注意事项:
- 使用
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. 输入输出(I/O)
Rust 的标准库提供了丰富的输入输出功能,涵盖控制台、文件、网络等。I/O 操作通常返回 Result 类型,需要正确处理可能的错误。
5.1 标准输入输出
- 标准输出:使用
println!和print!宏进行格式化输出。println!("Hello, {}!", "Rust"); // 自动换行 print!("Enter your name: "); // 不换行 - 标准输入:使用
std::io模块读取用户输入。use std::io; let mut input = String::new(); io::stdin() .read_line(&mut input) .expect("Failed to read line"); println!("You entered: {}", input.trim()); - 格式化输出:支持多种格式化选项(已在第15章常用宏中详细介绍)。
let x = 42; let y = 3.14159; println!("x = {:04}, y = {:.2}", x, y); // x = 0042, y = 3.14
5.2 文件 I/O
- 读取文件:使用
std::fs::read_to_string读取整个文件为字符串。use std::fs; let content = fs::read_to_string("hello.txt") .expect("Failed to read file"); println!("File content: {}", content); - 写入文件:使用
std::fs::write写入字符串或字节。fs::write("output.txt", "Hello, file!") .expect("Failed to write file"); - 逐行读取:使用
BufReader高效读取大文件。use std::fs::File; use std::io::{BufRead, BufReader}; let file = File::open("data.txt").expect("Failed to open file"); let reader = BufReader::new(file); for line in reader.lines() { let line = line.expect("Failed to read line"); println!("{}", line); }
5.3 缓冲读写
- BufReader:减少系统调用,提高读取性能。
use std::io::BufReader; use std::fs::File; let file = File::open("large.txt")?; let mut reader = BufReader::new(file); let mut buffer = String::new(); reader.read_to_string(&mut buffer)?; - BufWriter:缓冲写入,减少写操作次数。
use std::io::{BufWriter, Write}; use std::fs::File; let file = File::create("output.txt")?; let mut writer = BufWriter::new(file); writer.write_all(b"Hello, buffered world!")?; writer.flush()?; // 确保所有数据写入磁盘
5.4 错误处理
- I/O 操作返回
Result<T, std::io::Error>,必须处理可能的错误。 - 使用
?运算符传播错误,或使用expect/unwrap在简单程序中快速失败。use std::fs::File; use std::io::Read; fn read_file(path: &str) -> Result<String, std::io::Error> { let mut file = File::open(path)?; let mut content = String::new(); file.read_to_string(&mut content)?; Ok(content) }
5.5 路径处理
- 使用
std::path::Path和PathBuf进行跨平台路径操作。use std::path::Path; let path = Path::new("/home/user/file.txt"); if path.exists() { println!("File exists"); }
常见场景与注意事项:
- 生产代码中避免使用
expect/unwrap,应妥善处理所有可能的错误。 - 大文件使用流式读取(
BufReader+ 逐行/逐块)避免内存溢出。 - 跨平台路径使用
Path/PathBuf而非字符串拼接。 - 网络 I/O 通常使用异步编程(见第19章异步编程)。
6. 所有权与借用
- 所有权:每个值有唯一所有者,离开作用域时释放。移动后原变量失效。
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; // 现在允许 - 之前的借用已结束
7. 结构体与枚举
- 结构体:
- 命名结构体:
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"));
8. 模式匹配
- 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); }
9. 错误处理
- 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)) }
10. 模块系统
- 使用
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::*;
11. 集合类型
- 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();
12. 泛型与 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...)") } }
13. 生命周期
- 确保引用有效,生命周期注解用
'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.";
14. 并发
- 线程:使用
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(); }
15. 常用宏
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 模式下检查
16. 智能指针
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 配合使用,避免循环引用,不能增加引用计数但可以升级为强引用(若仍存活)。
17. 迭代器与闭包
- 迭代器(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
18. 属性与条件编译
- 属性(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() {}
19. 异步编程
- 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; }
20. 宏系统
- 声明宏(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 不在作用域中
21. 工具链与生态
- 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); }
22. 不安全 Rust(Unsafe Rust)
当 Rust 的安全检查无法表达某些底层操作时,unsafe 关键字允许你绕过编译器的检查。使用 unsafe 时,你必须保证代码的安全性,编译器信任你的判断。
不安全块:在
unsafe块中可以进行以下操作:- 解引用裸指针
- 调用不安全函数或方法
- 访问或修改可变静态变量
- 实现不安全 trait
let mut num = 5; let r1 = &num as *const i32; // 裸指针 let r2 = &mut num as *mut i32; unsafe { println!("r1 is: {}", *r1); // 解引用裸指针 println!("r2 is: {}", *r2); }不安全函数:使用
unsafe fn声明,调用时必须在unsafe块中。unsafe fn dangerous() { // 实现细节... } unsafe { dangerous(); // 调用不安全函数 }外部函数接口(FFI):与 C 语言库交互。
extern "C" { fn abs(input: i32) -> i32; } fn main() { unsafe { println!("Absolute value of -3: {}", abs(-3)); } }裸指针类型:
*const T(不可变)和*mut T(可变)。- 可以跨越线程共享(不检查 Send/Sync)
- 可以为空(使用
std::ptr::null()和std::ptr::null_mut()) - 解引用必须在
unsafe块中进行
let mut value = 42; let ptr = &mut value as *mut i32; let mutable_ref = unsafe { &mut *ptr }; *mutable_ref = 100;联合体(Union):使用
union声明,所有字段共享同一内存空间。#[repr(C)] union IntOrFloat { as_int: i32, as_float: f32, } fn main() { let mut u = IntOrFloat { as_int: 42 }; unsafe { println!("as int: {}", u.as_int); println!("as float: {}", u.as_float); } }
常见场景与注意事项:
尽量将
unsafe代码隔离在小型、独立的函数或模块中使用
unsafe时务必编写充分的注释说明为何安全结合
unsafe代码与安全抽象(如Vec<T>内部使用unsafe但对外提供安全 API`)使用
core::ptr::write、core::ptr::read等函数时注意别名的规则(strict provenance)CStr 与 CString:在 FFI 中安全地传递 C 字符串。
use std::ffi::{CStr, CString}; extern "C" { fn puts(s: *const libc::c_char); } fn main() { let cstring = CString::new("Hello from Rust!").unwrap(); unsafe { puts(cstring.as_ptr()); } }
23. 常用 trait 详解
Rust 标准库定义了许多重要的 trait,理解它们对于编写惯用代码至关重要。
Deref trait:允许自定义解引用行为。
use std::ops::Deref; struct MyBox<T>(T); impl<T> Deref for MyBox<T> { type Target = T; fn deref(&self) -> &Self::Target { &self.0 } } let x = 5; let y = MyBox::new(x); assert_eq!(5, *y); // 自动解引用- Deref 强制转换:当类型实现了
Deref,值引用会自动转换为内部类型的引用 - 常用于智能指针(如
Box<T>、Rc<T>、Arc<T>)
- Deref 强制转换:当类型实现了
Drop trait:自定义值被移出作用域时的行为(资源清理)。
struct File { name: String, } impl Drop for File { fn drop(&mut self) { println!("Dropping file: {}", self.name); } } fn main() { let _f = File { name: "data.txt".to_string() }; // 当离开作用域时,drop 被调用 }- 字段按声明顺序逆序 drop
- 不能手动调用
drop,应使用std::mem::drop函数
Default trait:为类型提供默认值。
#[derive(Default)] struct Config { host: String, port: u16, } fn main() { let config = Config::default(); // 或使用结构体更新语法 let config = Config { port: 8080, ..Default::default() }; }From 和 Into trait:类型转换。
#[derive(Debug)] struct Point { x: i32, y: i32 } impl From<(i32, i32)> for Point { fn from(coords: (i32, i32)) -> Self { Point { x: coords.0, y: coords.1 } } } fn main() { let p = Point::from((10, 20)); let p: Point = (30, 40).into(); }AsRef 和 AsMut trait:借用转换。
fn process_path<P: AsRef<std::path::Path>>(path: P) { let path = path.as_ref(); // 使用 path... } fn main() { process_path("/home/user"); // &str 实现 AsRef<Path> process_path(std::path::Path::new("data.txt")); }ToString 和 FromStr trait:字符串转换。
use std::str::FromStr; #[derive(Debug, PartialEq)] struct Point { x: i32, y: i32 } impl FromStr for Point { type Err = String; fn from_str(s: &str) -> Result<Self, Self::Err> { let coords: Vec<i32> = s .split(',') .map(|n| n.parse().map_err(|_| "Invalid number".to_string())) .collect::<Result<_, _>>()?; match coords.as_slice() { [x, y] => Ok(Point { x: *x, y: *y }), _ => Err("Expected exactly two numbers".to_string()), } } } fn main() { let p = "10,20".parse::<Point>().unwrap(); println!("{} {:?}", p.to_string(), p); }
24. 线程本地存储与同步原语
线程本地存储(Thread Local Storage):使用
thread_local!宏声明。use std::cell::RefCell; use std::thread; thread_local! { static THREAD_COUNTER: RefCell<u32> = RefCell::new(0); } fn main() { THREAD_COUNTER.with(|c| *c.borrow_mut() = 10); let handles: Vec<_> = (0..4).map(|_| { thread::spawn(|| { THREAD_COUNTER.with(|c| { let mut counter = c.borrow_mut(); *counter += 1; println!("Thread counter: {}", *counter); }) }) }).collect(); for h in handles { h.join().unwrap(); } }OnceLock 与 OnceCell(Rust 1.70+):单次初始化的线程安全容器。
use std::sync::OnceLock; static CONFIG: OnceLock<Config> = OnceLock::new(); #[derive(Debug)] struct Config { port: u16, workers: usize } fn get_config() -> &'static Config { CONFIG.get_or_init(|| { Config { port: std::env::var("PORT").unwrap_or_default().parse().unwrap_or(8080), workers: std::thread::available_parallelism().unwrap().get(), } }) } fn main() { let config = get_config(); println!("Port: {}, Workers: {}", config.port, config.workers); }OnceLock:用于static声明的线程安全单次初始化OnceCell:非线程安全版本,适用于单线程get_or_init:首次访问时初始化set:尝试设置值,返回Resultget_or_try_init:支持返回Result的初始化
Barrier:使多个线程在某个点上同步。
use std::sync::{Arc, Barrier}; use std::thread; let barrier = Arc::new(Barrier::new(4)); let mut handles = vec![]; for _ in 0..4 { let barrier = Arc::clone(&barrier); handles.push(thread::spawn(move || { println!("before barrier"); barrier.wait(); // 所有线程到达这里后继续 println!("after barrier"); })); } for h in handles { h.join().unwrap(); }Scope 线程:在指定作用域内生成线程,可以安全地借用局部变量。
use std::thread; fn main() { let mut numbers = vec![1, 2, 3, 4]; thread::scope(|s| { // 在作用域内生成线程,可以借用 numbers s.spawn(|| { println!("Numbers: {:?}", numbers); }); s.spawn(|| { numbers.push(5); }); }); // 所有作用域线程完成后,numbers 再次可变 numbers.push(6); }
25. 常用外部 crate
Rust 生态系统丰富,以下是各领域常用的 crate。
命令行参数解析:
// 使用 clap(功能完整,最流行) use clap::{Parser, ArgAction}; #[derive(Parser, Debug)] #[command(name = "myapp")] struct Args { #[arg(short, long, default_value = "localhost")] host: String, #[arg(short, long, default_value_t = 8080)] port: u16, #[arg(action = ArgAction::SetTrue)] verbose: bool, } fn main() { let args = Args::parse(); println!("{:?}", args); }clap:功能完整,支持子命令、配置文件、自动帮助信息argh:轻量级,零依赖,适合简单场景structopt(已合并到 clap):基于结构体的声明式解析
序列化与反序列化:
// serde - 序列化框架 use serde::{Serialize, Deserialize}; #[derive(Serialize, Deserialize, Debug)] struct User { id: u64, username: String, active: bool, } fn main() -> Result<(), serde_json::Error> { let user = User { id: 1, username: "alice".to_string(), active: true, }; // 序列化为 JSON let json = serde_json::to_string(&user)?; println!("JSON: {}", json); // 反序列化 let parsed: User = serde_json::from_str(&json)?; println!("{:?}", parsed); Ok(()) }serde:序列化框架,支持 JSON、YAML、TOML、MessagePack 等serde_json:JSON 支持toml:TOML 配置文件解析yaml-rust:YAML 支持
日志记录:
// 使用 log + env_logger use log::{info, warn, error}; fn main() { env_logger::init(); info!("Starting application"); warn!("Configuration file not found, using defaults"); error!("Failed to connect to database"); // 格式化日志 info!("User {} logged in at {}:{:?}", "alice", "10:00", [1, 2, 3]); }log:日志 facade(接口)env_logger:基于环境变量配置的实现tracing:更先进的分布式追踪框架,支持 span 和事件
随机数生成:
use rand::{Rng, thread_rng, random}; fn main() { let mut rng = thread_rng(); // 范围随机数 let n: u32 = rng.gen_range(1..100); // 浮点数随机数 let f: f64 = rng.gen_range(0.0..1.0); // 随机布尔值 let b: bool = random(); // 从集合中随机选择 let choices = ["apple", "banana", "cherry"]; let choice = choices[rng.gen_range(0..choices.len())]; println!("n={}, f={}, b={}, choice={}", n, f, b, choice); }日期与时间处理:
use chrono::{Local, DateTime, Utc}; fn main() { let now = Local::now(); println!("Local time: {}", now); let utc: DateTime<Utc> = Utc::now(); println!("UTC time: {}", utc); // 格式化 println!("Formatted: {}", now.format("%Y-%m-%d %H:%M:%S")); // 解析 let parsed = DateTime::parse_from_rfc3339("2024-01-15T10:30:00Z").unwrap(); println!("Parsed: {}", parsed); // 时区转换 let tokyo = now.with_timezone(&chrono::FixedOffset::east(9 * 3600)); println!("Tokyo: {}", tokyo); }正则表达式:
use regex::Regex; fn main() { let re = Regex::new(r"(\d{4})-(\d{2})-(\d{2})").unwrap(); let date = "2024-01-15"; if let Some(caps) = re.captures(date) { println!("Year: {}, Month: {}, Day: {}", caps.get(1).unwrap().as_str(), caps.get(2).unwrap().as_str(), caps.get(3).unwrap().as_str()); } // 替换 let re2 = Regex::new(r"\s+").unwrap(); let result = re2.replace_all("a b c", "-"); println!("Replaced: {}", result); // "a-b-c" // 迭代匹配 let text = "foo1, foo2, foo3"; for mat in re.find_iter(text) { println!("Found: {}", mat.as_str()); } }HTTP 客户端与服务端:
// reqwest - HTTP 客户端 use reqwest; #[tokio::main] async fn main() -> Result<(), reqwest::Error> { let response = reqwest::Client::new() .get("https://httpbin.org/ip") .send() .await?; let body = response.text().await?; println!("Response: {}", body); Ok(()) }// actix-web - HTTP 服务端 use actix_web::{web, App, HttpServer, Responder}; async fn index() -> impl Responder { "Hello, World!" } #[tokio::main] async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() .route("/", web::get().to(index)) }) .bind("127.0.0.1:8080")? .run() .await }
26. 惯用模式与最佳实践
RAII(资源获取即初始化):利用作用域自动释放资源。
struct DatabaseConnection { conn: *mut libc::c_void, } impl DatabaseConnection { fn connect() -> Result<Self, &'static str> { // 模拟连接... Ok(DatabaseConnection { conn: std::ptr::null_mut() }) } } impl Drop for DatabaseConnection { fn drop(&mut self) { println!("Closing database connection"); // 实际关闭连接... } } fn process_data() { let db = DatabaseConnection::connect().unwrap(); // 使用数据库... // 当函数结束时,drop 被自动调用 }Builder 模式:用于复杂对象的构造。
struct UserBuilder { name: String, email: Option<String>, age: Option<u32>, active: bool, } impl UserBuilder { fn new(name: &str) -> Self { UserBuilder { name: name.to_string(), email: None, age: None, active: true, } } fn email(mut self, email: &str) -> Self { self.email = Some(email.to_string()); self } fn age(mut self, age: u32) -> Self { self.age = Some(age); self } fn build(self) -> Result<User, &'static str> { let email = self.email.ok_or("Email is required")?; Ok(User { name: self.name, email, age: self.age, active: self.active, }) } } #[derive(Debug)] struct User { name: String, email: String, age: Option<u32>, active: bool, } fn main() { let user = UserBuilder::new("alice") .email("alice@example.com") .age(30) .build() .unwrap(); println!("{:?}", user); }Newtype 模式:类型安全的封装。
struct UserId(u32); struct SessionId(u32); fn authenticate(user_id: UserId) { println!("Authenticating user {}", user_id.0); } fn create_session(session_id: SessionId) { println!("Creating session {}", session_id.0); } fn main() { let uid = UserId(123); let sid = SessionId(456); authenticate(uid); create_session(sid); // 不能混淆 UserId 和 SessionId // authenticate(sid); // 编译错误! }类型状态机:使用类型系统编码状态。
// 状态标记 struct Draft; struct Published; // 博文 struct Post<S> { content: String, _state: std::marker::PhantomData<S>, } impl Post<Draft> { fn new() -> Self { Post { content: String::new(), _state: std::marker::PhantomData, } } fn add_text(&mut self, text: &str) { self.content.push_str(text); } fn request_review(self) -> Post<Published> { Post { content: self.content, _state: std::marker::PhantomData, } } } impl Post<Published> { fn content(&self) -> &str { &self.content } } fn main() { let mut post = Post::<Draft>::new(); post.add_text("Hello"); let post = post.request_review(); println!("Published: {}", post.content()); // post.add_text("More"); // 编译错误!已发布不能修改 }错误类型封装:
Result中封装多种错误类型。use std::fmt; #[derive(Debug)] enum MyError { Io(std::io::Error), Parse(std::num::ParseIntError), Custom(String), } impl fmt::Display for MyError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { MyError::Io(e) => write!(f, "IO error: {}", e), MyError::Parse(e) => write!(f, "Parse error: {}", e), MyError::Custom(s) => write!(f, "Error: {}", s), } } } impl std::error::Error for MyError {} impl From<std::io::Error> for MyError { fn from(e: std::io::Error) -> Self { MyError::Io(e) } } impl From<std::num::ParseIntError> for MyError { fn from(e: std::num::ParseIntError) -> Self { MyError::Parse(e) } } fn read_and_parse(path: &str) -> Result<i32, MyError> { let content = std::fs::read_to_string(path)?; let num = content.trim().parse()?; Ok(num) }
27. Panic 与错误恢复
Panic 行为:
// unwrap - 提取值或 panic let opt: Option<i32> = Some(5); let val = opt.unwrap(); // 5 // let val = None.unwrap(); // panic! // expect - 带消息的 unwrap let val = opt.expect("Expected a value"); // unwrap_or - 提供默认值 let val = opt.unwrap_or(0); // 5 // unwrap_or_else - 提供计算默认值的闭包 let val = opt.unwrap_or_else(|| 10 * 2); // 5 // unwrap_or_default - 使用类型的 Default let opt_str: Option<String> = None; let val = opt_str.unwrap_or_default(); // ""Panic 时中止:
// Cargo.toml [profile.release] panic = "abort" // 减小二进制体积,不展开栈 // 或在代码中 std::panic::set_hook(Box::new(|info| { println!("Panic: {}", info); std::process::exit(1); }));捕获 Panic:
std::panic::catch_unwindfn may_panic() -> i32 { if rand::random() { panic!("Oops!"); } 42 } fn main() { let result = std::panic::catch_unwind(|| { may_panic() }); match result { Ok(value) => println!("Success: {}", value), Err(_) => println!("Caught panic"), } }- 注意:
catch_unwind只能捕获展开(unwinding)的 panic,不能捕获panic = "abort"或abort发出的 panic
- 注意:
Assert 宏:
// debug 断言(只在 debug 构建时检查) debug_assert!(x > 0); // release 断言(始终检查) assert!(x > 0, "x must be positive, got {}", x); // 相等/不等断言 assert_eq!(a, b); assert_ne!(a, b);
28. Cargo 工作空间与依赖管理
工作空间(Workspace):
# Cargo.toml(根目录) [workspace] members = [ "crates/backend", "crates/cli", "crates/common", ] resolver = "2" // 锁定依赖解析规则 # 共享依赖 [workspace.dependencies] tokio = { version = "1.0", features = ["full"] } serde = { version = "1.0", features = ["derive"] }# crates/backend/Cargo.toml [package] name = "backend" version.workspace = true edition.workspace = true [dependencies] tokio = { workspace = true } serde = { workspace = true }依赖特性(Features):
[features] default = ["std", "logging"] std = [] logging = ["dep:log"] ssl = ["dep:openssl"] [dependencies] log = { optional = true } openssl = { optional = true }条件编译:
#[cfg(feature = "ssl")] mod ssl { // SSL 相关代码... } #[cfg(feature = "logging")] fn setup_logging() { // 设置日志... }开发依赖:
[dev-dependencies] tempfile = "3.0" assert_fs = "1.0" proptest = "1.0"构建脚本:
// build.rs fn main() { // 通知 Cargo 重新运行的条件 println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=src/bindings/*"); // 设置环境变量供依赖使用 println!("cargo:rustc-cfg=feature=\"my_feature\""); // 编译 C 库 cc::Build::new() .file("src/native/lib.c") .compile("mylib"); }
29. 性能优化技巧
减少分配:
// 预分配容量 let mut vec = Vec::with_capacity(1000); // 使用栈分配的数组(如果大小已知) let mut arr = [0i32; 1024]; // 避免字符串连接时的中间分配 let mut s = String::with_capacity(100); s.push_str("Hello"); s.push(' '); s.push_str("World");内联与优化提示:
#[inline] fn small_function(x: i32) -> i32 { x + 1 } #[inline(always)] fn tiny_function() -> i32 { 42 } #[cold] fn unlikely_path() { // 不太可能执行的代码... } #[track_caller] fn assert_helper(condition: bool, msg: &str) { if !condition { panic!("{}", msg); } }迭代器优化:
// 使用迭代器适配器(零成本抽象) let sum: i32 = (0..1000) .filter(|&x| x % 2 == 0) .map(|x| x * x) .sum(); // 使用预估容量 let v: Vec<_> = iter.collect_with_capacity_hint(size_hint);内存布局优化:
// 使用 repr(C) 或 repr(packed) 调整内存布局 #[repr(C)] struct PackedData { a: u8, // b: u32, // 如果不打包,可能会有填充 } #[repr(packed)] struct NoPadding { a: u8, b: u32, } // 使用 Option 优化 #[repr(u8)] enum MyEnum { A = 0, B = 1, }并行与并发优化:
use rayon::prelude::*; fn parallel_sum(data: &[i32]) -> i32 { data.par_iter().sum() } fn parallel_map(data: Vec<i32>) -> Vec<i32> { data.into_par_iter() .map(|x| x * 2) .collect() }避免克隆:
// 不好:每次迭代都克隆 for s in strings.iter() { let owned = s.clone(); // 克隆 process(owned); } // 好:借用 for s in strings.iter() { process(s); // 借用 } // 好:使用引用计数共享 use std::sync::Arc; let shared = Arc::new(data); let handles: Vec<_> = (0..4).map(|_| { let data = Arc::clone(&shared); std::thread::spawn(move || process(&data)) }).collect();
30. 常见陷阱与调试技巧
闭包捕获陷阱:
let mut funcs: Vec<fn() -> i32> = Vec::new(); for i in 0..5 { funcs.push(|| i * 2); // 错误!闭包捕获了 i } // 解决方案:复制值 for i in 0..5 { funcs.push(move || i * 2); }循环引用陷阱:
use std::rc::Rc; use std::cell::RefCell; struct Node { value: i32, next: RefCell<Option<Rc<Node>>>, } let a = Rc::new(Node { value: 1, next: RefCell::new(None) }); let b = Rc::new(Node { value: 2, next: RefCell::new(None) }); // 创建循环引用 - 内存泄漏! a.next.borrow_mut().replace(Rc::clone(&b)); b.next.borrow_mut().replace(Rc::clone(&a)); // 使用 Weak 打破循环 struct NodeWithWeak { value: i32, next: RefCell<Option<Rc<NodeWithWeak>>>, prev: RefCell<Option<std::rc::Weak<NodeWithWeak>>>, }借用检查器陷阱:
let mut v = vec![1, 2, 3]; // 不好:在借用期间修改 // let first = &v[0]; // v.push(4); // 错误!可能重新分配 // 好:先获取引用,再修改 let first = v[0]; v.push(4); // 好:使用索引代替引用 let idx = 0; v.push(v[idx] * 2);迭代器消费陷阱:
let v = vec![1, 2, 3]; // into_iter 消费所有权 for x in v.into_iter() { println!("{}", x); } // println!("{:?}", v); // 错误!v 已被消费 // iter 借用 let v = vec![1, 2, 3]; for x in v.iter() { println!("{}", x); } println!("{:?}", v); // 正常!v 仍可使用 // iter_mut 可变借用 for x in v.iter_mut() { *x *= 2; }调试技巧:
// 使用 dbg! 宏打印调试信息 let x = 5; let y = dbg!(x + 1); // 打印: [src/main.rs:3] x + 1 = 6 // 打印类型 let x = 5; println!("{:?}", std::any::type_name_of_val(&x)); // "i32" // 使用 Miri 检测未定义行为 // cargo +nightly install miri // cargo miri run // 使用 clippy 获取建议 // cargo clippy
23. 资源推荐
- The Rust Programming Language(官方书籍)
- Rust Reference(语言参考)
- Rust by Example(示例驱动学习)
- docs.rs(查看 crate 文档)

