简明Rust语法介绍
Rust 是一门注重内存安全、性能和并发性的系统编程语言。以下是其核心语法的扩展介绍,涵盖更多细节和实用特性,适合初学者快速了解并深入掌握。
变量与常量
变量:使用
let声明,默认不可变。使用mut允许修改。变量可以被遮蔽(重新声明)。1
2
3
4let 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 编译器能自动推断大多数类型,但在复杂情况下需要显式声明。
1
2
3let x = 5; // 推断为 i32
let y: f64 = 3.14; // 显式声明类型
let z = x as f64; // 类型转换
- 配置常量与全局值:不可变配置信息用
常量:使用
const声明,需指定类型,全局作用域,值在编译时确定。1
const MAX_VALUE: u32 = 100_000; // 下划线提高数字可读性常见场景与注意事项:
const适用于编译期已知、不依赖运行时环境的常量(如默认缓冲大小、协议常量)。- 如果需要运行时初始化的全局不可变值,使用
once_cell::sync::Lazy初始化static。
静态变量:使用
static声明,生命周期贯穿程序运行。1
static GREETING: &str = "Hello, Rust!";常见场景与注意事项:
static保存静态字符串、表驱动数据或与 FFI 交互时的固定地址数据。- 若需要可变静态数据,请使用
static搭配线程安全原语(如Atomic*、Mutex)或Lazy延迟初始化。 - 类型别名:使用
type关键字创建类型别名,提高代码可读性。1
2type Kilometers = i32;
type Result<T> = std::result::Result<T, String>; - 字面量后缀:使用后缀明确指定数值类型。
1
2
3let x = 42u8; // u8 类型
let y = 3.14f32; // f32 类型
let z = 100_000i64; // i64 类型,使用下划线提高可读性
数据类型
- 标量类型:
- 整数:有符号 (
i8,i16,i32,i64,i128),无符号 (u8,u16,u32,u64,u128),以及isize/usize(与系统架构相关)。 - 浮点数:
f32,f64。 - 布尔:
bool(true,false)。 - 字符:
char(Unicode,单引号,如'😊')。
- 整数:有符号 (
- 复合类型:
- 元组:固定长度,异构类型,可通过
.0,.1访问或解构。1
2let tup: (i32, f64, char) = (500, 6.4, 'z');
let (x, y, z) = tup; // 解构 - 数组:固定长度,同构类型,栈分配。
1
2let arr: [i32; 5] = [1, 2, 3, 4, 5];
let first = arr[0]; - 向量:动态数组,使用标准库
Vec<T>,堆分配。1
2let mut v = vec![1, 2, 3];
v.push(4); - 切片(Slice):对连续序列的"视图",不拥有数据,长度在运行时确定。常用于字符串与数组的局部访问,避免拷贝。
1
2
3
4
5
6
7let 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:字符串切片,不拥有数据。
1
2
3
4let s1 = String::from("hello");
let s2 = "world"; // &str 类型
let s3 = s1 + " " + s2; // String 拼接
let len = s3.len(); // 获取字节长度(非字符数) - 类型转换:
1
2
3let num_str = "42";
let num: i32 = num_str.parse().unwrap(); // 字符串转数字
let str_from_num = num.to_string(); // 数字转字符串
Range(范围)语法
Range 是 Rust 中用于表示序列范围的重要语法,广泛应用于循环迭代、切片操作和模式匹配中。
基本语法
半开区间
start..end:包含 start,不包含 end1
2
3for i in 1..5 { // 1, 2, 3, 4
println!("{}", i);
}闭区间
start..=end:包含 start 和 end1
2
3for i in 1..=5 { // 1, 2, 3, 4, 5
println!("{}", i);
}
切片中的 Range
Range 在切片操作中用于指定索引范围:
从起点开始
..end:从默认起点到 end(不包含)1
2let arr = [10, 20, 30, 40, 50];
let slice = &arr[..3]; // [10, 20, 30]到终点结束
start..:从 start 到终点(包含)1
2let arr = [10, 20, 30, 40, 50];
let slice = &arr[2..]; // [30, 40, 50]全范围
..:包含所有元素1
2let arr = [10, 20, 30, 40, 50];
let slice = &arr[..]; // [10, 20, 30, 40, 50]
Range 类型
Rust 标准库为不同的 range 语法定义了对应的类型:
| 语法 | 类型 | 说明 |
|---|---|---|
start..end | Range<T> | 半开区间 |
start..=end | RangeInclusive<T> | 闭区间 |
start.. | RangeFrom<T> | 从 start 到无限 |
..end | RangeTo<T> | 从起点到 end |
..=end | RangeToInclusive<T> | 从起点到 end(包含) |
.. | RangeFull | 全范围 |
1 | |
Range 的迭代与方法
Range 实现了 Iterator trait,可以直接用于循环,并支持多种迭代器方法:
1 | |
Range 在模式匹配中的应用
Range 可以用在 match 的模式中:
1 | |
常见场景与注意事项
切片越界:使用 range 进行切片时,确保索引不越界,或使用
get()方法进行安全访问。1
2
3let arr = [1, 2, 3];
// let slice = &arr[1..5]; // 运行时 panic!
let slice = arr.get(1..5); // 返回 None,安全空 range:当 start >= end 时(半开区间),range 为空,迭代时不会执行循环体。
1
2
3for i in 5..5 {
println!("这不会被执行");
}字符 Range:支持 Unicode 字符的范围迭代。
1
2
3for c in 'a'..='z' {
print!("{}", c); // abcdefghijklmnopqrstuvwxyz
}浮点数 Range:半开区间的浮点数 range 不能迭代,但闭区间可以。
1
2// for f in 0.0..1.0 {} // 错误!
for f in 0.0..=1.0 {} // 正确,但不推荐(浮点数精度问题)
函数
- 函数使用
fn定义,参数和返回值需显式指定类型。无返回值时省略->。1
2
3fn add(a: i32, b: i32) -> i32 {
a + b // 表达式返回值,无需 return
} - 发散函数:永不返回,使用
!作为返回类型(如panic!)。1
2
3fn diverge() -> ! {
panic!("This function never returns!");
}
引用参数
Rust 中函数参数默认按值传递(移动或复制)。为了传递引用避免所有权转移,使用 &T(不可变引用)或 &mut T(可变引用)。
不可变引用参数
&T:允许函数读取数据但不获取所有权,调用后原变量仍可使用。1
2
3
4
5
6
7
8
9
10fn print_length(s: &str) {
println!("长度: {}", s.len());
}
fn main() {
let s = String::from("hello");
print_length(&s); // 传递不可变引用
print_length(&s); // 可以再次使用,因为 s 的所有权没有被转移
println!("{}", s); // s 仍然有效
}可变引用参数
&mut T:允许函数修改传入的数据,调用时需使用&mut传递。1
2
3
4
5
6
7
8
9fn append_world(s: &mut String) {
s.push_str(" world");
}
fn main() {
let mut s = String::from("hello");
append_world(&mut s);
println!("{}", s); // "hello world"
}引用参数的借用规则:
- 同一时刻,可以有任意多个不可变引用
&T - 同一时刻,只能有一个可变引用
&mut T - 不可变引用和可变引用不能同时存在
1
2
3
4
5
6
7fn process(data: &mut Vec<i32>, item: &i32) {
// data.push(*item); // 错误!同时持有可变引用 data 和不可变引用 item
}
fn correct_process(data: &mut Vec<i32>, item: i32) {
data.push(item); // 正确:item 是按值传递
}- 同一时刻,可以有任意多个不可变引用
多引用参数与生命周期:当函数返回引用时,需要显式标注生命周期。
1
2
3
4
5
6
7
8
9
10
11// 返回两个字符串切片中较长的那个
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let s1 = String::from("hello");
let s2 = "world!";
let result = longest(&s1, s2);
println!("较长的字符串: {}", result);
}
可变参数
Rust 本身不支持 C 风格的可变参数函数,但可以通过以下方式实现类似功能:
使用切片
&[T]:接受同类型元素的切片。1
2
3
4
5
6
7
8
9fn sum(numbers: &[i32]) -> i32 {
numbers.iter().sum()
}
fn main() {
println!("{}", sum(&[1, 2, 3, 4, 5])); // 15
println!("{}", sum(&[10, 20])); // 30
println!("{}", sum(&[])); // 0
}使用泛型迭代器:接受任何可迭代的类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14fn sum_iter<I>(iter: I) -> i32
where
I: Iterator<Item = i32>,
{
iter.sum()
}
fn main() {
let v = vec![1, 2, 3];
println!("{}", sum_iter(v.into_iter())); // 6
// 支持范围
println!("{}", sum_iter(1..=5)); // 15
}使用宏实现可变参数:最灵活的方式,支持不同类型和数量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14macro_rules! print_all {
// 基本情况:空参数
() => {};
// 递归情况:处理第一个参数,然后递归处理剩余参数
($first:expr $(, $rest:expr)*) => {
print!("{} ", $first);
print_all!($($rest),*);
};
}
fn main() {
print_all!(1, "hello", 3.14, true);
println!();
}使用
vec!风格的宏(更实用的可变参数宏):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15macro_rules! max {
($x:expr) => { $x };
($x:expr, $($y:expr),+) => {
{
let x = $x;
let y = max!($($y),+);
if x > y { x } else { y }
}
};
}
fn main() {
println!("{}", max!(5)); // 5
println!("{}", max!(1, 3, 2, 5, 4)); // 5
}
引用参数的进阶用法
自动解引用与强制转换
Rust 会自动解引用(Deref coercion)以简化函数调用:
1 | |
引用参数的重新借用(Reborrowing)
可变引用可以重新借用,允许在同一作用域内多次使用:
1 | |
引用与模式匹配
在函数参数中可以直接使用模式匹配解构:
1 | |
可变参数的更多实现方式
使用 IntoIterator trait
比直接使用 Iterator 更灵活,接受任何可转换为迭代器的类型:
1 | |
使用 FromIterator 构建集合
配合可变参数模式,可以构建任意集合类型:
1 | |
可变参数的宏模式(进阶)
更复杂的宏模式,支持键值对参数:
1 | |
函数参数的所有权策略
接收器模式(Receiver Patterns)
根据函数对参数的处理方式选择合适的接收器:
1 | |
AsRef 与 AsMut 参数
使用 trait bound 让函数更通用:
1 | |
Cow(写时克隆)作为参数
避免不必要的克隆,仅在需要修改时才复制:
1 | |
常见场景与注意事项:
- 引用 vs 所有权:优先使用引用参数
&T或&mut T避免不必要的克隆;只有当函数需要获取所有权(如构建新数据结构)时才使用值传递。 - 函数签名设计:接受
&str而非&String,接受&[T]而非&Vec<T>,这样函数更通用,可以接收更广泛的输入类型。 - 可变参数的权衡:切片方式
&[T]性能最好但有类型限制;泛型迭代器最灵活但编译器可能生成更多代码;宏最强大但调试困难。 - 小函数优先:把复杂逻辑拆成小函数,提高可测试性与可读性,便于单元测试。
- 错误传播与返回类型:库函数应尽量返回
Result<T, E>,避免在库内部panic!,将不可恢复错误留在应用层或测试。 - 函数指针:可以将函数作为参数传递或存储在变量中。
1
2
3fn add(a: i32, b: i32) -> i32 { a + b }
let operation: fn(i32, i32) -> i32 = add;
let result = operation(5, 3); - 高阶函数:接受函数作为参数或返回函数的函数。
1
2
3
4
5fn 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 是表达式语言,但也可以使用显式返回。
1
2
3
4
5
6fn early_return(x: i32) -> i32 {
if x > 10 {
return x * 2; // 提前返回
}
x + 1 // 隐式返回
}
控制流
- 条件语句:
if支持表达式风格返回值,无需三元运算符。1
2let number = 7;
let result = if number > 0 { "positive" } else { "non-positive" };- 条件必须是
bool类型。
- 循环:
loop:无限循环,可用break返回值。1
2
3
4
5
6
7let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};while:条件循环。1
2
3while counter < 15 {
counter += 1;
}for:常用于迭代器或范围。1
2
3for i in 0..5 { // 包含 0,不包含 5
println!("i: {}", i);
}- Range 迭代:Range 实现了
Iteratortrait,可以直接用于循环(详见第 3 章 Range 语法)。1
2
3for i in 0..5 { // 包含 0,不包含 5
println!("i: {}", i);
}
常见场景与注意事项:
- 使用
if表达式构造值可以避免可变变量,比如let status = if cond { a } else { b };。 - 选择合适的迭代方式:
iter()(借用)、iter_mut()(可变借用)、into_iter()(消费并取得所有权),以避免所有权相关编译错误。 loop+break值在需要复杂退出条件且最终返回计算结果时更清晰。- 模式匹配在控制流中的应用:
1
2
3
4
5
6let pair = (0, -2);
match pair {
(0, y) => println!("x=0, y={}", y),
(x, 0) => println!("x={}, y=0", x),
_ => println!("其他情况"),
} - 标签循环:可以给循环添加标签,用于明确指定 break/continue 的目标。
1
2
3
4
5
6
7
8'outer: for i in 0..3 {
'inner: for j in 0..3 {
if i == 1 && j == 1 {
break 'outer;
}
println!("({}, {})", i, j);
}
}
输入输出(I/O)
Rust 的标准库提供了丰富的输入输出功能,涵盖控制台、文件、网络等。I/O 操作通常返回 Result 类型,需要正确处理可能的错误。
标准输入输出
- 标准输出:使用
println!和print!宏进行格式化输出。1
2println!("Hello, {}!", "Rust"); // 自动换行
print!("Enter your name: "); // 不换行 - 标准输入:使用
std::io模块读取用户输入。1
2
3
4
5
6
7use std::io;
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("Failed to read line");
println!("You entered: {}", input.trim()); - 格式化输出:支持多种格式化选项(已在第15章常用宏中详细介绍)。
1
2
3let x = 42;
let y = 3.14159;
println!("x = {:04}, y = {:.2}", x, y); // x = 0042, y = 3.14
文件 I/O
- 读取文件:使用
std::fs::read_to_string读取整个文件为字符串。1
2
3
4
5use std::fs;
let content = fs::read_to_string("hello.txt")
.expect("Failed to read file");
println!("File content: {}", content); - 写入文件:使用
std::fs::write写入字符串或字节。1
2fs::write("output.txt", "Hello, file!")
.expect("Failed to write file"); - 逐行读取:使用
BufReader高效读取大文件。1
2
3
4
5
6
7
8
9use 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);
}
缓冲读写
- BufReader:减少系统调用,提高读取性能。
1
2
3
4
5
6
7use 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:缓冲写入,减少写操作次数。
1
2
3
4
5
6
7use 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()?; // 确保所有数据写入磁盘
错误处理
- I/O 操作返回
Result<T, std::io::Error>,必须处理可能的错误。 - 使用
?运算符传播错误,或使用expect/unwrap在简单程序中快速失败。1
2
3
4
5
6
7
8
9use 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)
}
路径处理
- 使用
std::path::Path和PathBuf进行跨平台路径操作。1
2
3
4
5
6use 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章异步编程)。
所有权与借用
- 所有权:每个值有唯一所有者,离开作用域时释放。移动后原变量失效。
1
2
3let s1 = String::from("hello");
let s2 = s1; // s1 失效
// println!("{}", s1); // 错误:s1 已移动 - 借用:使用
&创建不可变引用,&mut创建可变引用。同一时间只能有一个可变引用或多个不可变引用。1
2
3let mut s = String::from("hello");
let r1 = &s; // 不可变借用
// let r2 = &mut s; // 错误:不可同时有可变和不可变借用 - 复制:实现了
Copytrait 的类型(如基本类型)不会移动。1
2let x = 5;
let y = x; // 复制,x 仍有效
常见场景与注意事项:
- 函数参数设计:若函数不需要修改数据,使用
&T;若需要修改则&mut T;若函数需要获得所有权(例如构建返回值),传T。 - Clone 的成本:对大型结构频繁
clone()会有性能开销,优先考虑借用或使用共享引用(Rc/Arc)。 - 临时值借用:不要返回对局部
String的&str—— 这会导致借用指向超出作用域的值(编译器错误)。 - 常见陷阱:在同一作用域内同时持有不可变借用与可变借用(例如在遍历同时修改容器)会被编译器拒绝。
- 借用检查器规则总结:
- 规则1:任何借用必须在其引用的值的生命周期内有效。
- 规则2:可以同时存在多个不可变借用 (
&T)。 - 规则3:不能同时存在可变借用 (
&mut T) 和任何其他借用。
1
2
3
4
5
6
7let mut s = String::from("hello");
let r1 = &s; // 不可变借用
let r2 = &s; // 另一个不可变借用 - 允许
// let r3 = &mut s; // 错误:不能同时有可变借用
println!("{} {}", r1, r2);
// r1 和 r2 在这里不再使用
let r3 = &mut s; // 现在允许 - 之前的借用已结束
结构体与枚举
- 结构体:
- 命名结构体:
1
2
3
4
5struct Rectangle {
width: u32,
height: u32,
}
let rect = Rectangle { width: 30, height: 50 }; - 元组结构体:
1
2struct Color(u8, u8, u8);
let black = Color(0, 0, 0); - 方法:使用
impl定义。1
2
3
4
5impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
- 命名结构体:
- 枚举:支持复杂数据结构,常与
match结合。1
2
3
4
5enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
}
常见场景与注意事项:
- 枚举用于状态机建模(例如连接状态、任务状态),结合
match可保证覆盖所有分支。 - DTO 与业务实体:接口层返回轻量 DTO,避免将内部可变状态或锁直接暴露给调用者。
- impl 方法签名:不可变操作使用
&self,需要修改使用&mut self,并根据可能的错误返回Result。 - 关联函数(不取 self 参数):
1
2
3
4
5
6impl Rectangle {
fn square(size: u32) -> Self {
Self { width: size, height: size }
}
}
let sq = Rectangle::square(10); - 枚举的常见模式:
1
2
3
4
5
6enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
模式匹配
- match:穷尽匹配,必须覆盖所有情况。
1
2
3
4
5
6let num = Some(4);
match num {
Some(x) if x % 2 == 0 => println!("Even: {}", x),
Some(x) => println!("Odd: {}", x),
None => println!("None"),
} - if let:简化单一模式匹配。
1
2
3if let Some(x) = num {
println!("Value: {}", x);
}
常见场景与注意事项:
- 使用
if let和while let处理单一分支或逐项解析,代码更简洁。 - 在匹配复杂结构时使用解构(
{ x, y, .. }或(_, _, tail)),并用_/..忽略不必要字段。 - 当有默认分支时尽量显式写出
_ => {},以便未来扩展时不遗漏处理。 - 守卫条件(match guards):
1
2
3
4
5
6let num = Some(4);
match num {
Some(x) if x < 5 => println!("小于5: {}", x),
Some(x) => println!("大于等于5: {}", x),
None => println!("没有值"),
} - 绑定模式:
1
2
3
4
5
6let value = Some(10);
if let Some(x) = value {
println!("值是: {}", x);
} else {
println!("没有值");
} - while let 循环:
1
2
3
4let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
println!("弹出: {}", top);
}
错误处理
- Option:处理可能为空的值 (
Some(T)或None)。1
2let opt: Option<i32> = Some(5);
let value = opt.unwrap_or(0); // 解包或提供默认值 - Result:处理可能出错的操作 (
Ok(T)或Err(E))。1
2
3
4
5
6
7
8fn 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 - ? 运算符:简化错误传播。
1
2
3
4fn 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来附带额外上下文,便于排查问题。 - 组合子方法:
1
2
3
4
5
6let 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) - 错误类型转换:
1
2
3
4
5
6
7
8
9fn 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))
}
模块系统
- 使用
mod定义模块,pub控制可见性,use引入路径。1
2
3
4
5
6
7mod 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 或把共享接口移动到顶层模块来解决。
- 模块路径系统:
1
2
3
4
5
6// 绝对路径
crate::front_of_house::hosting::add_to_waitlist();
// 相对路径
front_of_house::hosting::add_to_waitlist();
// 使用 super 访问父模块
super::some_function(); - 使用 as 关键字重命名:
1
2use std::collections::HashMap as Map;
let mut map = Map::new(); - 通配符导入(谨慎使用):
1
use std::collections::*;
集合类型
- Vec:动态数组。
1
2let mut v = Vec::new();
v.push(1); - HashMap:键值对集合。
1
2
3use 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等并发数据结构减少锁竞争。 - 其他常用集合:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18use 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 - 迭代器适配器:
1
2
3let 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();
泛型与 trait
- 泛型:提高代码复用性。
1
2
3fn largest<T: PartialOrd>(list: &[T]) -> &T {
&list[0]
} - trait:定义共享行为,类似接口。
1
2
3
4
5
6
7
8
9
10
11trait 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),并考虑为常见类型提供专门实现。
- 关联类型:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15trait 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()
}
} - 默认实现:
1
2
3
4
5trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
生命周期
- 确保引用有效,生命周期注解用
'a表示。1
2
3fn 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的生命周期被赋予所有输出生命周期参数
1
2
3
4
5
6
7
8
9
10// 编译器可以自动推断,无需显式生命周期注解
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[..]
} - 静态生命周期:
1
let s: &'static str = "I have a static lifetime.";
并发
- 线程:使用
std::thread。1
2
3
4
5use std::thread;
let handle = thread::spawn(|| {
println!("Running in a thread!");
});
handle.join().unwrap(); - 消息传递:通过通道 (
mpsc)。1
2
3
4use std::sync::mpsc;
let (tx, rx) = mpsc::channel();
tx.send(42).unwrap();
println!("Received: {}", rx.recv().unwrap()); - 共享状态:使用
Mutex和Arc.1
2use std::sync::{Arc, Mutex};
let counter = Arc::new(Mutex::new(0));
常见场景与注意事项:
- 任务划分:IO 密集型使用异步模型(tokio),CPU 密集型使用线程池或
spawn_blocking。 - 共享计数器:若只是计数,优先使用原子类型(
AtomicUsize)以减少锁开销。 - 通道模式:
mpsc适合生产者-消费者;对性能敏感场景可用crossbeam提供更高性能的通道。 - 避免死锁:尽量缩小锁的持有范围,并避免在持锁期间调用外部代码或再加锁。
- 读写锁(RwLock):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16use 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):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17use 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();
}
常用宏
println!,format!:格式化输出。vec!:快速创建向量。panic!:触发程序崩溃。todo!:标记未实现代码。1
2let v = vec![1, 2, 3];
println!("{:?}", v);
常见场景与注意事项:
- 调试:使用
dbg!(expr)快速打印表达式及其位置,便于排查。 - 宏滥用:宏能生成重复代码,但会增加调试难度;如果函数或 trait 已能满足需求,优先使用函数/trait。
- 自定义宏:在需要元编程或减少重复模板化代码(如注册、绑定)时使用,但注意错误消息友好性。
- 格式化宏详解:
1
2
3
4
5
6
7
8let name = "Rust";
let version = 1.70;
println!("语言: {}, 版本: {}", name, version); // 基本格式化
println!("二进制: {:b}", 10); // 1010
println!("十六进制: {:x}", 255); // ff
println!("指针: {:p}", &name); // 内存地址
println!("调试: {:?}", (1, 2, 3)); // 调试格式 - 断言宏:
1
2
3
4assert!(true);
assert_eq!(2 + 2, 4);
assert_ne!(1, 2);
debug_assert!(some_condition); // 仅在 debug 模式下检查
智能指针
Rust 中的“智能指针”不仅仅是指针,它们还可以提供额外的所有权语义与运行时行为。常见的智能指针包括 Box<T>、Rc<T>、Arc<T>、Cell<T> 与 RefCell<T>。下面分别介绍它们的用途、约束与示例。
Box
:将值放到堆上,最简单的智能指针。常用于: - 在编译时大小不确定但需要在堆上存储(如递归数据结构)。
- 明确将大对象移到堆以减小栈开销。
1
2let 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)。- 使用场景:树结构、图结构或需要多个所有者但只在单线程中的共享数据。
1
2
3
4
5use 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的计数开销更高,但可用于并发场景。1
2
3
4
5
6
7
8
9
10
11
12
13
14use 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。不提供引用借用;适合小型标量或实现缓存的场景。1
2
3
4
5use 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)的场景,例如构建可变的树/图。
1
2
3
4
5
6
7
8
9
10
11
12
13
14use 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>(弱引用)来打破循环:1
2
3
4use 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(写时克隆):
1
2
3
4
5
6
7
8
9use 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(高级特性):
用于确保某些类型在内存中的位置不会被移动,主要用于异步编程。1
2
3
4use std::pin::Pin;
let mut val = 5;
let pinned = Pin::new(&mut val);
小结:
- Box:将值放到堆上(所有权明确)。
- Rc:单线程引用计数,多所有者(只读或配合内部可变性)。
- Arc:跨线程的原子引用计数。
- Cell / RefCell:在单线程下提供“内部可变性”,在运行时检查借用规则(RefCell 会 panic 当规则被违反)。
- Weak:与 Rc/Arc 配合使用,避免循环引用,不能增加引用计数但可以升级为强引用(若仍存活)。
迭代器与闭包
- 迭代器(Iterator):Rust 的
for循环本质就是迭代器语法糖。任何实现Iterator的类型都可以用for遍历。1
2
3let v = vec![1, 2, 3];
let mut iter = v.iter(); // 产生 &i32
assert_eq!(iter.next(), Some(&1));- 常用适配器:
map、filter、collect、fold、enumerate、zip等,均返回新迭代器,实现“零成本抽象”。
1
let sum: i32 = (1..=10).filter(|&x| x % 2 == 0).sum();- 自定义迭代器:实现
Iterator关联类型Item与next方法即可。
- 常用适配器:
- 闭包(Closure):匿名函数,可捕获环境变量,语法
|param| body。1
2
3let factor = 2;
let multiply = |x| x * factor;
assert_eq!(multiply(5), 10);- 闭包会根据捕获方式自动实现
Fn、FnMut或FnOnce三种 trait,决定可被调用多次/可变/一次性。 - 常与迭代器搭配,实现链式数据处理:
1
2
3
4let v: Vec<i32> = vec![1, 2, 3]
.into_iter()
.map(|x| x * x)
.collect(); - 闭包会根据捕获方式自动实现
常见场景与注意事项:
- 流式处理:用迭代器链实现数据转换流水线,既简洁又高效(惰性求值)。
- 惰性计算:迭代器直到被消费(如
collect、sum、for)才执行,适合处理大数据/流式数据。 - 捕获语义:闭包默认按需借用或移动捕获变量;遇到所有权冲突时可使用
move强制移动捕获。 - 迭代器消费者:
1
2
3
4
5
6
7
8
9
10
11let 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:只能调用一次,获取所有权
1
2
3
4
5
6
7
8fn 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
属性与条件编译
- 属性(Attribute):以
#[...]形式给编译器提供元数据。1
2
3
4
5#[derive(Debug, Clone)]
struct Point { x: f64, y: f64 }
#[inline(always)]
fn fast_add(a: i32, b: i32) -> i32 { a + b } - 条件编译:使用
#[cfg(...)]按平台/特性包含或排除代码。1
2
3
4
5#[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)]隔离测试辅助代码,避免污染生产可执行文件。 - 自定义属性:
1
2
3
4
5
6
7
8
9
10
11// 自定义条件编译属性
#[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() {} - 内联属性详解:
1
2
3
4#[inline] // 建议内联
#[inline(always)] // 总是内联
#[inline(never)] // 从不内联
fn example() {}
异步编程
- async/.await:Rust 使用零成本抽象的异步模型,基于
Futuretrait。1
2
3
4
5
6
7
8use 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 基础:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20use 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(实验性):
1
2
3
4#[async_trait::async_trait] // 需要 async-trait crate
trait AsyncService {
async fn process(&self, data: &str) -> String;
}
宏系统
- 声明宏(macro_rules!):基于模式匹配生成代码。
1
2
3
4
5
6macro_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、泛型实现可行的逻辑。
- 过程宏示例:
1
2
3
4
5
6
7
8
9
10// 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 宏是卫生的,意味着它们不会意外捕获外部作用域的标识符。1
2
3
4
5
6
7
8macro_rules! make_function {
() => {
fn helper() -> i32 { 42 }
};
}
make_function!();
// helper(); // 错误:helper 不在作用域中
工具链与生态
- 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):
1
2
3
4
5
6# Cargo.toml
[workspace]
members = [
"crate-a",
"crate-b",
] - 发布配置优化:
1
2
3
4
5# Cargo.toml
[profile.release]
lto = true # 启用链接时优化
codegen-units = 1 # 单代码生成单元
panic = "abort" # 移除 panic 处理代码 - 常用开发命令:
1
2
3
4
5cargo tree # 查看依赖树
cargo expand # 查看宏展开结果
cargo metadata # 获取项目元数据
cargo fix # 自动修复编译警告
cargo miri # 运行 Miri 检测未定义行为 - 交叉编译:
1
2
3
4
5# 安装目标平台
rustup target add x86_64-unknown-linux-musl
# 为特定目标构建
cargo build --target x86_64-unknown-linux-musl - 自定义构建脚本:
1
2
3
4
5
6
7
8
9
10// 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");
} - 环境变量:
1
2
3
4
5
6
7
8// 在编译时获取环境变量
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);
}
不安全 Rust(Unsafe Rust)
当 Rust 的安全检查无法表达某些底层操作时,unsafe 关键字允许你绕过编译器的检查。使用 unsafe 时,你必须保证代码的安全性,编译器信任你的判断。
不安全块:在
unsafe块中可以进行以下操作:- 解引用裸指针
- 调用不安全函数或方法
- 访问或修改可变静态变量
- 实现不安全 trait
1
2
3
4
5
6
7
8let 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块中。1
2
3
4
5
6
7unsafe fn dangerous() {
// 实现细节...
}
unsafe {
dangerous(); // 调用不安全函数
}外部函数接口(FFI):与 C 语言库交互。
1
2
3
4
5
6
7
8
9extern "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块中进行
1
2
3
4
5let mut value = 42;
let ptr = &mut value as *mut i32;
let mutable_ref = unsafe { &mut *ptr };
*mutable_ref = 100;联合体(Union):使用
union声明,所有字段共享同一内存空间。1
2
3
4
5
6
7
8
9
10
11
12
13#[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 字符串。
1
2
3
4
5
6
7
8
9
10
11
12use 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());
}
}
常用 trait 详解
Rust 标准库定义了许多重要的 trait,理解它们对于编写惯用代码至关重要。
Deref trait:允许自定义解引用行为。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15use 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:自定义值被移出作用域时的行为(资源清理)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14struct 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:为类型提供默认值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14#[derive(Default)]
struct Config {
host: String,
port: u16,
}
fn main() {
let config = Config::default();
// 或使用结构体更新语法
let config = Config {
port: 8080,
..Default::default()
};
}From 和 Into trait:类型转换。
1
2
3
4
5
6
7
8
9
10
11
12
13#[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:借用转换。
1
2
3
4
5
6
7
8
9fn 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:字符串转换。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25use 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);
}
线程本地存储与同步原语
线程本地存储(Thread Local Storage):使用
thread_local!宏声明。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24use 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+):单次初始化的线程安全容器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20use 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:使多个线程在某个点上同步。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18use 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 线程:在指定作用域内生成线程,可以安全地借用局部变量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18use 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);
}
常用外部 crate
Rust 生态系统丰富,以下是各领域常用的 crate。
命令行参数解析:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// 使用 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):基于结构体的声明式解析
序列化与反序列化:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26// 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 支持
日志记录:
1
2
3
4
5
6
7
8
9
10
11
12
13// 使用 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 和事件
随机数生成:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20use 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);
}日期与时间处理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20use 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);
}正则表达式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24use 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 客户端与服务端:
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 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(())
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 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
}
惯用模式与最佳实践
RAII(资源获取即初始化):利用作用域自动释放资源。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23struct 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 模式:用于复杂对象的构造。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55struct 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 模式:类型安全的封装。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21struct 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); // 编译错误!
}类型状态机:使用类型系统编码状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45// 状态标记
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中封装多种错误类型。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38use 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)
}
Panic 与错误恢复
Panic 行为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 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 时中止:
1
2
3
4
5
6
7
8
9// Cargo.toml
[profile.release]
panic = "abort" // 减小二进制体积,不展开栈
// 或在代码中
std::panic::set_hook(Box::new(|info| {
println!("Panic: {}", info);
std::process::exit(1);
}));捕获 Panic:
std::panic::catch_unwind1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17fn 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 宏:
1
2
3
4
5
6
7
8
9// debug 断言(只在 debug 构建时检查)
debug_assert!(x > 0);
// release 断言(始终检查)
assert!(x > 0, "x must be positive, got {}", x);
// 相等/不等断言
assert_eq!(a, b);
assert_ne!(a, b);
Cargo 工作空间与依赖管理
工作空间(Workspace):
1
2
3
4
5
6
7
8
9
10
11
12
13# 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"] }1
2
3
4
5
6
7
8
9# crates/backend/Cargo.toml
[package]
name = "backend"
version.workspace = true
edition.workspace = true
[dependencies]
tokio = { workspace = true }
serde = { workspace = true }依赖特性(Features):
1
2
3
4
5
6
7
8
9[features]
default = ["std", "logging"]
std = []
logging = ["dep:log"]
ssl = ["dep:openssl"]
[dependencies]
log = { optional = true }
openssl = { optional = true }条件编译:
1
2
3
4
5
6
7
8
9#[cfg(feature = "ssl")]
mod ssl {
// SSL 相关代码...
}
#[cfg(feature = "logging")]
fn setup_logging() {
// 设置日志...
}开发依赖:
1
2
3
4[dev-dependencies]
tempfile = "3.0"
assert_fs = "1.0"
proptest = "1.0"构建脚本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 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");
}
性能优化技巧
减少分配:
1
2
3
4
5
6
7
8
9
10
11// 预分配容量
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");内联与优化提示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19#[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);
}
}迭代器优化:
1
2
3
4
5
6
7
8// 使用迭代器适配器(零成本抽象)
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);内存布局优化:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// 使用 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,
}并行与并发优化:
1
2
3
4
5
6
7
8
9
10
11use 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()
}避免克隆:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 不好:每次迭代都克隆
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();
常见陷阱与调试技巧
闭包捕获陷阱:
1
2
3
4
5
6
7
8
9let 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);
}循环引用陷阱:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21use 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>>>,
}借用检查器陷阱:
1
2
3
4
5
6
7
8
9
10
11
12
13let 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);迭代器消费陷阱:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19let 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;
}调试技巧:
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 使用 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
资源推荐
- The Rust Programming Language(官方书籍)
- Rust Reference(语言参考)
- Rust by Example(示例驱动学习)
- docs.rs(查看 crate 文档)


