前端工具链的发展,一个明显的趋势------越来越多的高性能前端工具正在用 Rust 重写。Vite 8 现在已经用 Rolldown 作为默认打包器;Biome、Oxc 等工具也在用 Rust 构建,提供更快的 Lint 和格式化体验。
Rust 是一门系统编程语言,由 Mozilla 于 2010 年发布,2015 年发布 1.0 稳定版。它的核心目标有三个:
- 内存安全:不需要垃圾回收(GC)就能保证内存安全
- 高性能:性能媲美 C/C++
- 并发安全:编译时就能防止数据竞争
变量
一、命名规则
- 使用蛇形命名 (snake_case):
my_variable_name - 常量使用全大写加下划线:
MAX_SIZE - 类型名使用驼峰命名:
MyStruct
二、变量绑定的本质?
在 Rust 中,你并不是"声明一个变量",而是将一个值绑定到一个标识符 上。这个过程叫变量绑定(variable binding)。
rs
fn main() {
let a = 10;
println!("a = {}", a)
}

三、变量在使用前必须被初始化
rs
fn main() {
let a:i32;
println!("a = {}", a);
}

rs
fn main() {
let a:i32;
a = 10;
println!("a = {}", a); // 正常打印
}
四、变量的不可变性
Rust 变量最显著的特点:默认是不可变的(immutable) 。这意味着一旦绑定了一个值,就不能改变它。
rs
fn main() {
let a = 10; // 将10绑定到a变量上
a = 11; // 不能直接修改a的值,因为a是不可变的
println!("a = {}", a)
}

如果你想修改变量的值,需要显式地使用 mut 关键字。
rs
fn main() {
let mut a = 10;
println!("a = {}", a);
a = 11; // 可以修改a的值,因为a是可变的
println!("a = {}", a);
}
五、变量遮蔽
变量遮蔽是指在代码中,你可以重新绑定一个变量到一个新的值,而不会影响到之前绑定的值。
重新声明同名变量,可改变类型,创建新绑定。
rs
fn main() {
let a = 10; // 将10绑定到a变量上
println!("a = {}", a); // a = 10
let a = 20; // 将20绑定到a变量上
println!("a = {}", a); // a = 20
}
js
fn main() {
let a = 10; // 将10绑定到a变量上
println!("a = {}", a); // a = 10
let a = "cds"; // 将20绑定到a变量上
println!("a = {}", a); // a = 20
}
六、变量的作用域
Rust 的变量作用域是块作用域 (由 {} 界定)。一个绑定从它声明的位置开始,到当前块结束为止有效。
rs
fn main() {
{
let a = 10;
println!("a = {}", a);
} // a 在这里被销毁
println!("a = {}", a);
}

七、所有权
Rust 最独特的点:变量绑定拥有它绑定的值。所有权规则:
- 每个值在 Rust 中有一个所有者(owner)。
- 同一时刻只能有一个所有者。
- 当所有者离开作用域,值会被丢弃。
rs
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1 的所有权被移动到 s2
// 此后 s1 不再有效
println!("{}", s2);
println!("{}", s1);
}

数据类型
Rust 是一门静态类型 语言,意味着所有变量的类型必须在编译时已知。不过得益于类型推断,通常不需要显式标注类型,编译器会根据上下文自动推导。
Rust 的数据类型可分为两大类:标量类型 (Scalar Types)和复合类型(Compound Types),此外还有自定义类型、引用、切片等高级类型。
标量类型
标量类型代表单个值。Rust 有四种基本的标量类型:整数、浮点数、布尔值和字符。
一、整数类型(Integer)
整数是没有小数部分的数字。Rust 提供了多种长度的有符号和无符号整数。
i32 是默认整数类型,通常性能最好。
isize 和 usize 的长度取决于运行程序的计算机架构(64位系统上为64位,32位系统上为32位),主要用于集合索引和内存偏移。
| 长度 | 有符号 | 无符号 |
|---|---|---|
| 8位 | i8 |
u8 |
| 16位 | i16 |
u16 |
| 32位 | i32 |
u32 |
| 64位 | i64 |
u64 |
| 128位 | i128 |
u128 |
| 架构相关 | isize |
usize |
二、浮点数类型(Floating-Point)
Rust 有两种浮点数:f32(单精度)和 f64(双精度)。f64 是默认类型 ,因为现代 CPU 上它的速度与 f32 相近,但精度更高。
rs
fn main() {
let s:f32 = 3.1415926;
println!("s = {}", s);
}

控制输出格式
- 科学计数法 :使用
{:e}或{:E}。 - 固定宽度 :使用
{:width.precision}控制宽度和精度。 - 对齐 :在宽度前加
-。
rs
fn main() {
let s: f32 = 3.1415926;
println!("s = {:.4}", s); // 保留.4位小数
println!("s = {:e}", s); // 科学计数法
println!("s = {:-10.2}", s); // 保留.2位小数
}

三、布尔类型(Boolean)
布尔类型有两个值:true 和 false,占用一个字节。
rs
fn main() {
let a: bool = true;
let b: bool = false;
println!("a && b = {}", a && b); // 输出:false
println!("a || b = {}", a || b); // 输出:true
println!("!a = {}", !a); // 输出:false
}

四、字符类型(Character)
Rust 的 char 类型代表一个 Unicode 标量值,占用 4 个字节。它可以表示中文、日文、韩文、表情符号等。
注意:
char用单引号 ,字符串用双引号。
rs
fn main() {
let c1: char = 'A'; // 英文字母
let c2: char = '中'; // 中文字符
let c3: char = '😀'; // 表情符号(Emoji)
let c4: char = '1'; // 数字字符
let c5: char = '!'; // 标点符号
println!("c1 = {} c2 = {} c3 = {} c4 = {} c5 = {}", c1, c2, c3, c4, c5);
}
复合类型
复合类型可以将多个值组合成一个类型。Rust 有两个基本的复合类型:元组和数组。
一、元组(Tuple)
元组可以将多个不同类型的值组合成一个固定大小的序列。创建元组使用圆括号。
- 默认情况下,元组是 不可变 的。
- 使用
mut关键字可声明 可变元组,但只能修改元素的值,不能改变长度或类型。 - 单元素元组需要在元素后添加逗号,否则会被视为普通括号表达式。
- 空元组
()是一个特殊的元组,表示"无值"或"空"。
rs
fn main() {
// 定义一个包含整数、浮点数和字符串的元组
let tup: (i32, f64, &str) = (10, 3.14, "hello");
// 类型推断:Rust 会自动推断元组类型
let another_tup: (bool, char, i32) = (true, 'A', 42);
println!("tup 元素:{}、{}、 {}", tup.0, tup.1, tup.2);
let (a, b, c) = another_tup; // 解构
println!("another_tup 元素:a = {}, b = {}, c = {}", a, b, c);
}

rs
fn main() {
let mut tup = (10, 20);
tup.0 = 100;
println!("修改后:{:?}", tup);
}
单元素元组需要在元素后添加逗号,否则会被视为普通括号表达式。
rs
let single_tuple = (5,); // 单元素元组(正确)
let not_tuple = (5); // 只是整数 5(非元组)
println!("单元素元组:{:?}", single_tuple); // 输出:(5,)
空元组 () 是一个特殊的元组,表示"无值"或"空"。
rs
fn main() {
let result = greet();
println!("函数返回值:{:?}", result); // 输出:()
}
fn greet() -> () {
println!("Hello!");
}
二、 数组(Array)
数组中的每个元素类型必须相同,且长度固定。数组分配在栈上,而不是堆上。
- 默认情况下,数组是不可变的(元素值不可修改)。
- 使用
mut关键字可声明可变数组,但长度仍固定,只能修改元素值。
【示例】 声明一个数组
rs
fn main() {
// 声明一个包含 5 个 i32 类型元素的数组
let arr: [i32; 5] = [1, 2, 3, 4, 5];
println!("arr = {:?}", arr);
}

【示例】 声明一个数组
rs
fn main() {
// 声明一个包含 10 个元素的数组,每个元素值为 0
let arr: [i32; 10] = [0; 10];
println!("arr = {:?}", arr);
}

【示例】 修改数组元素
rs
fn main() {
let mut arr: [i32; 3] = [1, 2, 3];
arr[0] = 100;
println!("修改后:{:?}", arr);
}
【示例】 遍历数组
rs
fn main() {
let arr = [1, 2, 3, 4, 5];
println!("数组长度:{}", arr.len()); // 输出:5
let arr2 = [10, 20, 30];
for element in &arr2 {
println!("for遍历元素:{}", element);
}
let arr3 = [10, 20, 30];
for i in 0..arr3.len() {
println!("索引遍历 {}: {}", i, arr3[i]);
}
}
字符串类型
在 Rust 中,字符串类型主要分为两种:字符串切片(&str) 和 堆分配字符串(String) 。
字符串切片
&str:字符串切片,通常以字面量形式出现,存储在可执行文件的只读内存中。
1、字符串切片的基本特性?
- 不可变性 :
&str本身是不可变的(指向的数据不可修改)。 - 内存布局 :存储在栈 上,包含两个字段:
- 指向字符串数据的指针(
*const u8)。 - 字符串长度(
usize)。
- 指向字符串数据的指针(
- 无所有权 :
&str是引用类型,不拥有数据的所有权,仅临时借用。
2、如何创建字符串切片?
【示例】从字符串字面量创建,字符串字面量默认是 &'static str 类型(静态生命周期),存储在程序的只读内存区域。
rs
fn main() {
let static_slice: &str = "Hello, Rust!"; // 类型为 &'static str
println!("static_slice = {}", static_slice);
}
【示例】 从 String 转换,通过 as_str() 方法或直接引用 &s 将 String 转换为 &str。
js
fn main() {
let s: String = String::from("Hello");
let slice1: &str = s.as_str(); // 方法 1
let slice2: &str = &s; // 方法 2(更简洁)
println!("slice1 = {}", slice1);
println!("slice2 = {}", slice2);
}
堆分配字符串(String)
String 是一个可变的、堆分配的 UTF-8 字符串类型,长度可动态调整。
1、基本特性?
内存布局 :存储在堆上,包含三个字段(在栈上的结构体):
- 指向堆内存的指针(
*mut u8)。 - 字符串长度(
usize)。 - 堆内存容量(
usize,即已分配的空间大小)。
所有权 :String 拥有其数据的所有权,离开作用域时会自动释放堆内存。
2、创建字符串
【示例】空字符串 :使用 String::new()。
rs
fn main() {
// 1、创建一个空的字符串变量
let mut s: String = String::new();
s.push_str("Hello, Rust!"); // 追加字符串
println!("s = {}", s);
}
【示例】从字符串字面量创建 :使用 String::from() 或 to_string() 方法。
js
// 2、从字符串字面量创建
let s2: String = String::from("Hello, Rust!");
let s3: String = "Hello".to_string();
println!("s2 = {}", s2);
println!("s3 = {}", s3);
【示例】从 &str 创建。
rs
// 3、从&str创建
let slice: &str = "Hello";
let s4: String = String::from(slice);
println!("s4 = {}", s4);
3、字符串操作
【示例】 追加字符串 :使用 push_str() 方法。
rs
fn main() {
let mut s = String::from("Hello");
s.push_str(", Rust!"); // 结果:"Hello, Rust!"
}
【示例】 追加单个字符 :使用 push() 方法。
js
let mut s1 = String::from("Hello");
s1.push('!'); // 结果:"Hello!"
println!("s1 = {}", s1);
【示例】使用 + 运算符 :+ 运算符右侧必须是 &str,且会消耗左侧的 String 所有权。
js
let ss1 = String::from("Hello");
let ss2 = String::from(" Rust");
let ss3 = ss1 + &ss2; // ss1 被消耗,ss3 是新的 String
println!("ss3 = {}", ss3);
【示例】使用 format! 宏:更灵活的拼接方式,不消耗原有字符串。
js
let s_1 = String::from("Hello");
let s_2 = String::from("Rust");
let s_3 = format!("{} {}", s_1, s_2); // 结果:"Hello Rust"
println!("s_3 = {}", s_3);
引用
在 Rust 中,引用(Reference) 是一种无需获取所有权就能访问数据的方式,是 Rust 所有权系统的核心组成部分。引用允许我们临时借用数据的访问权,而不会转移所有权,从而避免了不必要的复制和内存开销。
引用是指向数据的指针,通过 & 符号创建,类型为 &T(不可变引用)或 &mut T(可变引用)。
- 无所有权:引用不拥有数据的所有权,因此离开作用域时不会释放数据。
- 借用:引用的过程称为"借用"(Borrowing)。
- 生命周期:引用的生命周期受限于其指向的数据,确保引用始终有效。
借用规则(Borrowing Rules)
Rust 的借用规则确保了引用的安全性,避免了数据竞争:
- 不可变引用规则 :
- 可以同时存在多个不可变引用(共享访问)。
- 不可变引用存在时,不能创建可变引用(避免数据竞争)。
- 可变引用规则 :
- 同一时间只能有一个可变引用(独占访问)。
- 可变引用存在时,不能创建不可变引用(避免数据竞争)。
- 引用作用域 :
- 引用的作用域从创建开始,到最后一次使用结束。
- 作用域结束后,引用会被自动释放,允许创建新的引用。
悬垂引用(Dangling References)
悬垂引用是指向已释放内存的引用,Rust 通过生命周期系统防止这种情况。
产生原因?当引用指向的数据在引用仍然有效时被释放,就会产生悬垂引用。
【示例】函数返回局部变量的引用
rs
fn dangle() -> &String {
let s = String::from("Hello"); // s 是局部变量,存储在栈上
&s // 尝试返回 s 的引用
} // s 离开作用域,被自动释放(内存被回收)
fn main() {
let r = dangle(); // r 成为悬垂引用,指向已释放的内存
println!("{}", r); // 访问已释放的内存,可能导致未定义行为
}

4、正确的解决方案
【示例】返回所有权而非引用。如果需要从函数返回数据,直接转移所有权。
rs
// 返回所有权而非引用
fn dangle() -> String {
let s = String::from("Hello"); // s 是局部变量,存储在栈上
s // 尝试返回 s 的所有权
}
fn main() {
let r = dangle();
println!("{}", r);
}
【示例】使用静态生命周期('static)。如果数据需要在整个程序运行期间存在,可以使用 'static 生命周期
rs
// 使用静态生命周期('static)
fn dangle() -> &'static str {
"hello"
}
fn main() {
let r = dangle();
println!("{}", r);
}
【示例】使用 Box<T> 或其他智能指针。对于需要在堆上分配且生命周期较长的数据,可以使用 Box<T> 等智能指针。
rs
// 使用 Box<T>
fn dangle() -> Box<i32> {
Box::new(42) // 返回 Box<i32>,所有权转移给调用者
}
fn main() {
let r = dangle();
println!("{}", r);
}
自定义类型
在 Rust 中,自定义类型主要通过以下几种方式实现:结构体(Struct) 、枚举(Enum) 、联合(Union) 和 类型别名(Type Alias) 。
结构体(struct)
结构体是一种将多个相关值打包在一起的自定义数据类型。Rust 有三种结构体:具名字段结构体、元组结构体、单元结构体。
一、具名字段结构体
具名结构体 是结构体最常见的形态------每个字段都有名称 和类型。
rs
#[derive(Debug)]
struct User {
#[allow(dead_code)]
active: bool,
#[allow(dead_code)]
username: String,
#[allow(dead_code)]
email: String,
#[allow(dead_code)]
sign_in_count: u64,
}
fn main() {
// 创建实例,必须为所有字段提供值
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
println!("{:?}", user1);
}
尝试使用 {:?} 格式化输出 User 类型的值(例如在 println!("{:?}", user) 或 dbg! 宏中),但 User 类型没有实现 Debug trait。Rust 要求类型显式实现 Debug 才能以调试格式打印。

User 结构体的某些字段被定义了,但在代码中从未被读取 (即从未访问过它们的值)。这属于 dead_code lint(死代码警告),默认开启。
快速修复:自动派生 Debug。在 User 定义上方添加 #[derive(Debug)]。如果某些字段暂时不需要,添加 #[allow(dead_code)]。

【示例】通过 impl 添加方法
rs
fn main() {
struct User {
username: String,
email: String,
}
impl User {
fn get_name(&self) -> (&str, &str) {
(&self.username, &self.email)
}
}
let user = User {
username: String::from("lili"),
email: String::from("lili@example.com"),
};
println!("username = {}, email = {}", user.get_name().0, user.get_name().1);
}

二、元组结构体
【示例】字段没有名字,只有类型。
rs
fn main() {
#[allow(dead_code)]
struct Point(i32, i32);
#[derive(Debug)]
#[allow(dead_code)]
struct Color(u8, u8, u8);
let origin = Point(0, 0);
let red = Color(255, 0, 0);
println!("x = {}", origin.0); // 通过索引访问
println!("{:?}", red);
}
三、单元结构体 Unit-Like Struct
单元结构体在内存中不占用任何空间 (零大小类型,ZST - Zero Sized Type),但它是一个具体的类型,可以用来表达某种"标记"或"状态"。
rs
fn main() {
#[derive(Debug)]
struct Once; // 定义一个单元结构体
let x = Once; // 创建实例,不需要 `{}` 或 `()`
let y = Once; // 可以有多个实例,但它们是完全相同的"空"值
println!("{:?}", x);
println!("{:?}", y);
}
枚举(enum)
枚举定义一个类型可能取值的集合,每个变体可以带有不同类型和数量的数据。枚举是 Rust 中实现"代数数据类型"的核心。
rs
#[derive(Debug)]
enum Direction {
Up,
#[allow(dead_code)]
Down,
#[allow(dead_code)]
Left,
#[allow(dead_code)]
Right,
}
#[derive(Debug)]
enum Message {
#[allow(dead_code)]
Quit, // 无数据
#[allow(dead_code)]
Move { x: i32, y: i32 }, // 匿名结构体(见下面)
#[allow(dead_code)]
Write(String), // 元组变体
#[allow(dead_code)]
ChangeColor(u8, u8, u8), // 元组变体
}
#[derive(Debug)]
enum Shape {
#[allow(dead_code)]
Circle { radius: f64 },
#[allow(dead_code)]
Rectangle { width: f64, height: f64 },
}
fn main() {
let dir = Direction::Up;
let msg = Message::Write(String::from("hello"));
let circle = Shape::Circle { radius: 10.0 };
println!("{:?}", dir);
println!("{:?}", msg);
println!("{:?}", circle);
}
联合体(union)
联合体类似于 C 语言的 union,所有字段共享同一块内存。
Rust 中的联合体是 unsafe 的,需要放在 unsafe 块中访问。极少使用,主要用于 FFI(外部函数接口)或极度追求内存布局的场景。
通常建议用枚举代替联合体,除非明确需要节省内存并与 C 代码交互。
rs
union MyUnion {
i: i32,
f: f32,
}
fn main() {
let u = MyUnion { i: 42 };
unsafe {
println!("{}", u.i); // 42
println!("{}", u.f); // 未定义行为?把 i32 位模式解释为 f32
}
}

类型别名(type alias)
使用 type 关键字为现有类型创建新名字。类型别名不创建新类型,只是别名,因此可以互换使用。
注意:类型别名是 弱类型(只是别名),如果需要真正的类型区分,应使用元组结构体或 newtype 模式。
rs
fn main() {
type Kilometers = i32;
let x: i32 = 5;
let y: Kilometers = 5;
println!("x + y = {}", x + y); // 合法,因为它们是同一类型
}
控制语句
控制语句是编程语言的基础,用于决定程序的执行路径。Rust 的控制流设计强调表达式优先 (几乎所有控制结构都是表达式)、安全性 (如 match 必须穷举所有可能)和简洁性。
条件语句:if 与 else
if 表达式根据条件执行分支。条件必须是 bool 类型,Rust 不会自动将整数或指针转换为布尔值。
rs
fn main() {
let x: i32 = 5;
if x > 0 {
println!("positive");
} else if x < 0 {
println!("negative");
} else {
println!("zero");
}
}
重要特性 :if 是表达式,可以用于赋值。所有分支必须返回相同类型的值。 注意:if 作为表达式时,分支末尾不能有分号,否则会变成 () 类型。
rs
fn main() {
let x: i32 = 5;
let number: i32 = if x > 0 { 1 } else { -1 };
println!("number = {}", number);
}
循环:loop、while、for
【示例】loop 重复执行一个代码块,直到遇到 break。它是 Rust 中最基础的循环,常用于需要明确退出条件的场景。
rs
fn main() {
let mut count: i32 = 0;
loop {
count += 1;
if count == 3 {
break; // 退出循环
}
}
println!("count = {}", count);
}
【示例】loop 也可以作为表达式,通过 break 返回值:
rs
fn main() {
let result: i32 = loop {
let mut input: String = String::new();
std::io::stdin().read_line(&mut input).unwrap();
if let Ok(num) = input.trim().parse::<i32>() {
break num; // 返回解析成功的数字
}
};
println!("You entered: {}", result);
}
【示例】while 在条件为 true 时重复执行。
rs
fn main() {
let mut x: i32 = 3;
while x > 0 {
println!("{}", x);
x -= 1;
}
}
【示例】for 用于遍历迭代器,是 Rust 中最常用的循环,因为不容易出错(不会越界)。
注意 :for 会消耗迭代器,默认会获得元素的所有权。如果只想借用,可以使用 .iter() 或 &collection。
rs
fn main() {
// 遍历范围
for i in 0..5 {
// 0,1,2,3,4
println!("{}", i);
}
// 包含上界
for i in 0..=5 {
// 0,1,2,3,4,5
println!("{}", i);
}
// 遍历集合
let arr = [10, 20, 30];
for elem in arr.iter() {
println!("{}", elem);
}
// 遍历集合(获取索引和值)
for (idx, val) in arr.iter().enumerate() {
println!("arr[{}] = {}", idx, val);
}
}
break、continue 与循环标签
break:立即退出当前循环。continue:跳过本次循环剩余部分,进入下一次迭代。
【示例】当有嵌套循环时,可以使用循环标签 ('label)来指定作用于哪个循环。
rs
fn main() {
'outer: for i in 0..3 {
for j in 0..3 {
if i == 1 && j == 1 {
break 'outer; // 直接退出外层循环
}
println!("({}, {})", i, j);
}
}
// 输出: (0,0) (0,1) (0,2) (1,0)
}
模式匹配 match
match 是 Rust 中最强大的控制流结构,它将一个值与一系列模式进行匹配,并根据匹配的模式执行代码。match 必须穷举所有可能的情况。
rs
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn main() {
let nickel_value = value_in_cents(Coin::Nickel);
let dime_value = value_in_cents(Coin::Dime);
let quarter_value = value_in_cents(Coin::Quarter);
let penny_value = value_in_cents(Coin::Penny);
println!("nickel_value = {}", nickel_value);
println!("dime_value = {}", dime_value);
println!("quarter_value = {}", quarter_value);
println!("penny_value = {}", penny_value);
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1, // 单行表达式
Coin::Nickel => {
println!("Nickel");
5 // 多行用花括号
}
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
函数
基本概念
1、函数定义与命名
使用 fn 关键字定义一个函数。Rust 采用 蛇形命名(snake_case)风格。
函数可以在任意位置定义(包括在 main 函数之后),Rust 没有"必须先声明后使用"的限制。
rs
// 无参数、无返回值
fn greet() {
println!("Hello!");
}
2、函数参数
Rust 是静态类型语言,每个参数都必须注明类型。
多个参数用逗号分隔。与变量一样,参数默认是不可变 的。如果需要修改参数的值,可以在函数体内用 mut 绑定
rs
// 带参数
fn main() {
add(1, 2);
}
fn add(a: i32, b: i32) {
println!("Sum: {}", a + b);
}
当参数为非引用类型时,调用函数会转移所有权(移动语义)
js
fn take_ownership(s: String) {
println!("{}", s);
} // s 离开作用域,被释放
fn main() {
let s = String::from("Hello");
take_ownership(s);
// println!("{}", s); // 错误:s 已失去所有权
}
使用引用参数可避免所有权转移,提高性能:
- 不可变引用 (
&T):允许多个读取者。 - 可变引用 (
&mut T):允许单个修改者。
js
// 不可变引用
fn print_length(s: &String) {
println!("Length: {}", s.len());
}
// 可变引用
fn append_world(s: &mut String) {
s.push_str(", World!");
}
fn main() {
let s1 = String::from("Hello");
print_length(&s1); // 传递不可变引用
println!("s1: {}", s1); // s1 仍然可用
let mut s2 = String::from("Hello");
append_world(&mut s2); // 传递可变引用
println!("s2: {}", s2); // 输出:Hello, World!
}
3、返回值
Rust 区分语句(statements)和表达式(expressions)。 语句执行操作但不返回值(如 let x = 5;,以分号结尾)。表达式计算出一个值(如 5、x + y、{ ... } 块)。
【示例】使用箭头 -> 声明返回类型。函数的最后一个表达式的值会被隐式返回 ,也可以使用 return 关键字提前返回。
rs
fn main() {
let sum = add(1, 2);
println!("Sum: {}", sum);
}
// 隐式返回
fn add(a: i32, b: i32) -> i32 {
a + b
}
【示例】返回多个值。Rust 不支持直接返回多个值,但可以使用元组。
rs
// 返回单元类型 ()
fn no_return() -> () {
println!("No return value");
}
嵌套函数
Rust 允许在函数内部定义嵌套函数(也称为局部函数)。
js
fn outer() {
fn inner() {
println!("Inner function");
}
inner(); // 调用嵌套函数
}
fn main() {
outer(); // 输出:Inner function
}
函数可以递归调用自身,但需要显式指定返回类型。
rs
fn factorial(n: u32) -> u32 {
if n <= 1 {
1
} else {
n * factorial(n - 1)
}
}
fn main() {
println!("5! = {}", factorial(5)); // 输出:120
}
函数指针
函数指针(function pointer)是 Rust 中一种指向函数定义的类型,允许你将函数作为参数传递、存储在变量中或作为返回值返回。它是 Rust 支持"函数是一等公民"的一种方式。
【示例】 函数指针作为参数
Rust 中,函数本身也是一等公民,可以作为参数传递或存储在变量中。函数类型用 fn 关键字表示(小写)。
rs
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn subtract(a: i32, b: i32) -> i32 {
a - b
}
// 接受函数指针作为参数
// 函数指针的类型使用 fn 关键字(小写),后跟参数和返回值类型
fn calculate(operation: fn(i32, i32) -> i32, a: i32, b: i32) -> i32 {
operation(a, b)
}
fn main() {
let sum = calculate(add, 10, 5);
let diff = calculate(subtract, 10, 5);
println!("Sum: {}, Diff: {}", sum, diff); // 输出:Sum: 15, Diff: 5
}
【示例】 函数指针作为返回值
js
fn get_math_op(op: char) -> fn(i32, i32) -> i32 {
match op {
'+' => add,
'-' => subtract,
_ => panic!("unknown op"),
}
}
fn add(x: i32, y: i32) -> i32 {
x + y
}
fn subtract(x: i32, y: i32) -> i32 {
x - y
}
fn main() {
let f: fn(i32, i32) -> i32 = get_math_op('+');
println!("{}", f(5, 3)); // 8
}
【示例】函数项类型,function item type
在 Rust 中,每个具体的函数都有一个独一无二的、零大小 的类型,称为函数项类型(function item type)。它不是函数指针,而是一个编译时确定的、无额外运行时空开销的类型。
当需要将函数作为值传递时,函数项类型会隐式强制转换 为函数指针类型 fn。
rs
fn main() {
fn foo() {
println!("foo");
}
let a = foo; // a 的类型是函数项类型(零大小)
let b: fn() = foo; // b 是函数指针
a();
b();
}


闭包函数
闭包(Closure)是 Rust 中一种可以捕获周围环境变量的匿名函数。
闭包使用 || 定义,竖线内是参数,后面跟着函数体。函数体可以是一个表达式(隐式返回)或一个语句块(用 {} 包裹)。
rs
let add_one = |x: i32| -> i32 { x + 1 }; // 完整类型注解
let add_one = |x| x + 1; // 类型推断,隐式返回
let print = || println!("Hello"); // 无参数
rs
fn main() {
let x = 5;
// 闭包捕获 x
let add_x = |y| x + y;
println!("5 + 3 = {}", add_x(3)); // 输出:8
}
Rust 闭包如何捕获变量取决于闭包体中对变量的使用方式。编译器会自动推导并选择合适的捕获方式:不可变借用 、可变借用 或移动所有权。
【示例】如果闭包只读取变量,则会以不可变借用方式捕获。
js
fn main() {
let list: Vec<i32> = vec![1, 2, 3];
let only_borrow = || println!("{:?}", list);
only_borrow();
println!("Still accessible: {:?}", list); // ✅ 可以继续使用
}
【示例】可变借用
js
fn main() {
let mut counter = 0;
let mut inc = || counter += 1; // 捕获 &mut counter
inc();
inc();
println!("{}", counter); // 2
}
【示例】移动所有权
js
fn main() {
let data = vec![1, 2, 3]; // data 变量拥有这个向量的所有权
// std::thread::spawn() 创建一个新的线程
// move 关键字将 data 的所有权从主线程转移到新线程中。
//为什么? 确保新线程可以安全访问 data,避免主线程结束后 data 被销毁导致的悬垂引用。
std::thread::spawn(move || {
println!("{:?}", data); // data 所有权移入线程
})
.join()
.unwrap();
// data 不能再使用
}
切片
切片是对集合中一段连续元素的引用,不拥有所有权。例如字符串切片(&str)和数组切片(&[T])。
数组切片
- 切片是数组的一个可变或不可变视图(引用),长度可动态调整。
- 语法:
&arr[start..end](包含 start,不包含 end)。 - 切片类型为
&[T](不可变)或&mut [T](可变)。
rs
fn main() {
// 不可变切片
let arr: [i32; 5] = [1, 2, 3, 4, 5];
let slice: &[i32] = &arr[1..3];
println!("切片:{:?}", slice);
// 可变切片
let mut arr2: [i32; 5] = [1, 2, 3, 4, 5];
let mut_slice: &mut [i32] = &mut arr2[1..4];
mut_slice[0] = 10;
println!("修改后数组:{:?}", arr2);
}
切片 (如 &[i32] 或 &mut [i32])是对原数据的引用 (Reference),而非对数据的所有权 (Ownership)获取。 引用的作用是临时借用数据的访问权,而不会转移数据的所有权。

Rust 的借用规则:
- 不可变引用 (
&T):可以有多个不可变引用同时存在(共享访问)。 - 可变引用 (
&mut T):同一时间只能有一个可变引用,且不能与不可变引用同时存在(独占访问)。
rs
fn main() {
let arr: [i32; 5] = [1, 2, 3, 4, 5];
let slice: &[i32] = &arr[1..3]; // 创建从索引 1 到 3 的切片(元素 2, 3)
let slcie2:&[i32] = &arr[1..4]; // 创建从索引 1 到 4 的切片(元素 2, 3, 4)
println!("切片:{:?}", slice); // 输出:[2, 3]
println!("切片:{:?}", slcie2); // 输出:[2, 3, 4]
}
字符串切片
见上文的字符串类型部分。
集合与泛型
Rust 的标准库提供了丰富的集合类型 (collections),它们都是泛型的------可以存储任意类型的元素。集合与泛型紧密结合,让你能够高效地操作数据,同时保持类型安全。
Vec<T>
Vec<T> 是 Rust 标准库中最常用的动态数组 类型,位于 std::vec::Vec。它可以在堆上分配一块连续内存,并允许在运行时增长或收缩。
- 动态大小 :元素个数可以在运行时变化(
push、pop、insert、remove等)。 - 连续内存:元素在内存中紧密排列,缓存友好,支持 O(1) 索引访问。
- 所有权 :
Vec<T>拥有其元素的所有权,当Vec被销毁时,其所有元素也会被销毁。 - 泛型 :可以存储任何类型的元素
T。 - 尾部操作高效 :
push和pop的均摊时间复杂度为 O(1)。 - 中间插入/删除:时间复杂度 O(n),因为需要移动后续元素。
【示例】 空 Vec
rs
fn main() {
let v1: Vec<i32> = Vec::new();
let v2 = Vec::<i32>::new();
// 使用类型推断
let mut v = Vec::new();
v.push(1); // 编译器推断出 v 是 Vec<i32>
println!("{:?}", v);
println!("{:?}", v1);
println!("{:?}", v2);
}
【示例】 使用宏 vec!
rs
fn main() {
let v1 = vec![1, 2, 3]; // Vec<i32>
let v2 = vec![0; 10]; // 包含 10 个 0 的 Vec<i32>
println!("{:?}", v1);
println!("{:?}", v2);
}
【示例】基本操作
rs
fn main() {
let mut v = Vec::new();
v.push(42);
let x = v.pop();
// 过索引获取元素,返回类型为 `Option<&T>`(不可变引用)
let first = v.get(0);
println!("{:?}", x); // Some(42)
println!("{:?}", first); // None
}
VecDeque<T>
VecDeque<T>(双端队列)是 Rust 标准库提供的一个可增长的双端队列 ,位于 std::collections::VecDeque。它支持在头部和尾部以 O(1) 时间复杂度高效地插入或删除元素,非常适合实现队列(先进先出)或双端队列(两端操作)场景。
rs
use std::collections::VecDeque;
// 空队列
let mut deque: VecDeque<i32> = VecDeque::new();
rs
// 预分配容量(避免频繁扩容)
let mut deque = VecDeque::with_capacity(100);
rs
// 从数组创建 (Rust 1.56+)
let deque = VecDeque::from([1, 2, 3]);
rs
// 从 Vec 转换
let vec = vec![1, 2, 3];
let deque: VecDeque<_> = vec.into_iter().collect();
HashMap<K, V>
HashMap 是 Rust 标准库中最常用的键值存储集合,位于 std::collections::HashMap。它基于哈希表实现,提供平均 O(1) 的插入、查找和删除操作。
注意:HashMap 的键必须实现 Hash 和 Eq trait,值可以是任意类型。
rs
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
// 插入键值对
map.insert("key".to_string(), 100);
let value = map.get("key"); // Option<&V>
// 最后一次使用不可变引用(作用域结束)
println!("{:?}", value);
let or_default = map.entry("key".to_string()).or_insert(0);
println!("{:?}", or_default);
}

错误是 Rust 借用规则的经典冲突:当存在不可变引用时,无法创建可变引用 。
Rust 的借用规则规定:同一时间不能同时存在不可变引用和可变引用。
【示例】创建与插入
rs
use std::collections::HashMap;
fn main() {
// 显式创建空 HashMap
let mut scores: HashMap<String, u32> = HashMap::new();
// 使用 insert 插入键值对
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Red"), 25);
println!("{:?}", scores);
// 使用宏 from 创建(Rust 1.56+)
// 同名变量 scores,会覆盖之前的 scores
let scores = HashMap::from([("Blue".to_string(), 10), ("Red".to_string(), 25)]);
println!("{:?}", scores);
}
BTreeMap<K, V>
BTreeMap<K, V> 是 Rust 标准库提供的基于 B 树的有序键值映射 ,位于 std::collections::BTreeMap。它根据键的顺序(通过 Ord trait)存储元素,支持高效的范围查询、有序遍历和日志时间复杂度的插入、删除、查找。
HashSet<T>
HashSet<T> 是 Rust 标准库提供的基于哈希表的无序集合 ,位于 std::collections::HashSet。它存储不重复的值,并支持平均 O(1) 的插入、删除和查找操作。
BTreeSet<T>
BTreeSet<T> 是 Rust 标准库提供的基于 B 树的有序集合 ,位于 std::collections::BTreeSet。它存储不重复的值 ,并且始终按照元素的顺序(通过 Ord trait)进行排序)。
BinaryHeap<T>
BinaryHeap<T> 是 Rust 标准库提供的基于二叉堆的优先级队列 ,位于 std::collections::BinaryHeap。它是一个最大堆 (max-heap),意味着堆顶始终是集合中的最大值 。如果需要最小堆,可以通过自定义 Ord 实现或使用 std::cmp::Reverse 包装类型。
LinkedList<T>
LinkedList<T> 是 Rust 标准库提供的双向链表 实现,位于 std::collections::LinkedList。它是一个线性集合,每个元素都包含指向前一个和后一个元素的指针。
重要提示 :在绝大多数 Rust 使用场景中,LinkedList 性能不佳 ,通常应优先使用 Vec<T> 或 VecDeque<T>。只有在需要频繁的中间插入/删除 且同时需要在链表两端进行高效操作 时,才考虑使用链表。即便如此,现代 CPU 缓存特性使得连续内存的 VecDeque 往往更快。
Trait
Trait 是 Rust 中定义共享行为 的核心机制。你可以把它理解为其他语言中的 接口(interface) ,但功能更强大:它支持默认实现、关联类型、泛型约束,并且能同时用于静态分发和动态分发。
【示例】带默认实现的 Trait:实现时可以不重写。
rs
trait Greeter {
fn greet(&self) {
println!("Hello!"); // 默认实现
}
}
struct Robot;
impl Greeter for Robot {} // 直接使用默认实现
fn main() {
let r = Robot;
r.greet(); // Hello!
}
【示例】定义与实现
rs
// Trait 定义
trait Speak {
fn speak(&self); // 只有签名,无默认实现
fn greet(&self) {
// 可以带默认实现
println!("Hello!");
}
}
// Dog 结构体:是一个单元结构体(无字段)
struct Dog;
// 为 Dog 类型实现 Speak trait
impl Speak for Dog {
fn speak(&self) {
println!("Woof!");
}
// greet 使用默认实现
}
fn main() {
// 创建 Dog 实例:let d = Dog;(单元结构体的创建方式)
let d = Dog;
d.speak(); // Woof!
d.greet(); // Hello!
}
在 Rust 中,trait 可以作为函数参数,表示函数可以接受任何实现了该 trait 的类型 。这是 Rust 实现多态的核心方式,分为静态分发 和动态分发两种。
静态分发(编译时确定具体类型)
- 编译器为每种具体类型生成单独的函数副本(单态化)。
- 零运行时开销,可内联。
- 函数内部无法将参数存储到不同具体类型的容器中(类型单一)。
动态分发(运行时确定具体类型)
- 参数是 trait 对象(胖指针:数据指针 + 虚表指针)。
- 通过虚表动态调用方法,有轻微运行时开销。
- 可以将不同类型(只要实现同一 trait)传入同一函数调用。
- 常用于需要异构集合(如
Vec<&dyn Speaker>)的场景。
模式解构(Pattern Destructuring)
模式解构是 Rust 中一种强大的语法特性,它允许你将一个复合数据结构(如元组、结构体、枚举、数组等)分解成其组成部分 ,并同时绑定到变量。这常用于 match 表达式、if let、while let、函数参数、let 语句等场景。
【示例】元组解构
rs
let point = (3, 5);
let (x, y) = point; // 解构元组,x=3, y=5
println!("x={}, y={}", x, y);
【示例】解构结构体
rs
struct Point {
x: i32,
y: i32,
}
let p = Point { x: 10, y: 20 };
let Point { x, y } = p; // 字段名与变量名相同
let Point { x: a, y: b } = p; // 重命名变量
println!("x={}, y={}", x, y);
println!("a={}, b={}", a, b);
【示例】解构枚举
rs
fn main() {
enum Message {
#[allow(dead_code)]
Quit,
Move {
x: i32,
y: i32,
},
#[allow(dead_code)]
Write(String),
#[allow(dead_code)]
ChangeColor(u8, u8, u8),
}
let msg = Message::Move { x: 1, y: 2 };
match msg {
Message::Quit => println!("退出"),
Message::Move { x, y } => println!("移动到 ({},{})", x, y),
Message::Write(text) => println!("写入: {}", text),
Message::ChangeColor(r, g, b) => println!("RGB({},{},{})", r, g, b),
}
}
生命周期
生命周期注解语法
通常情况下,你不需要手动标注生命周期------Rust 编译器会自动推断。但在某些复杂场景,编译器无法确定引用之间的关系,就需要你显式标注。
生命周期注解使用撇号 ('a,读作"生命周期 a")表示,放在类型之前:
rs
&'a T // 生命周期为 'a 的不可变引用
&'a mut T // 生命周期为 'a 的可变引用
生命周期注解本身不改变引用的实际存活时间,只是让编译器理解各个引用之间的存活关系。
生命周期省略规则(Lifetime Elision)
为了减少冗余注解,Rust 有一组规则,可以在常见情况下自动推断生命周期。这些规则被称为生命周期省略规则。
规则适用于函数或方法的签名:
- 每个引用参数都有自己的生命周期 。
例如:fn foo(x: &i32)→ 等同于fn foo<'a>(x: &'a i32) - 如果只有一个输入生命周期,输出生命周期被赋予该生命周期 。
例如:fn foo(x: &i32) -> &i32→ 等同于fn foo<'a>(x: &'a i32) -> &'a i32 - 如果有多个输入生命周期,但其中一个是
&self或&mut self(方法),那么输出生命周期被赋予self的生命周期。这使方法更容易使用。
如果应用这三条规则后,仍然无法确定输出引用的生命周期,编译器就会报错,要求你手动标注。
&'static
在 Rust 中,&'static 是一个生命周期注解 ,表示静态生命周期 。 & 表示这是一个引用类型 (Reference)。 'static 表示该引用的生命周期是静态的 ,即在整个程序运行期间都有效。
存储位置?
字符串字面量 (如 "Hello")默认是 &'static str 类型,因为它们存储在程序的只读内存区域 (.rodata 段),在程序启动时加载,程序结束时释放。
静态变量 (使用 static 关键字声明)的引用也具有 &'static 生命周期。
rs
fn main() {
let s: &str = "Hello, Rust!";
let static_str: &'static str = "Static string";
println!("static_str = {}", static_str);
println!("s = {}", s);
}
rs
static GLOBAL_VAR: i32 = 42;
fn main() {
let global_ref: &'static i32 = &GLOBAL_VAR;
println!("Global variable: {}", global_ref); // 42
}
宏 macro_rules!
声明宏通过模式匹配来生成代码。
常用模式匹配标记
- item ,条目(函数、结构体等)
fn foo() {} - block, 代码块
{ ... } - stmt, 语句
let x = 1; - pat, 模式
Some(x) - expr, 表达式
2 + 2 - ty, 类型
u32 - ident, 标识符
foo - path, 路径
std::collections::HashMap - tt, 单个 token 树
任意 token 序列 - meta, 属性内的元项
cfg(test)
重复模式语法
$( ... )*--- 零次或多次$( ... )+--- 一次或多次$( ... ),*--- 零次或多次,元素间用逗号分隔$( ... );+--- 一次或多次,用分号分隔
【示例】 简化 println!
js
macro_rules! log {
($($arg:tt)*) => {
println!("[LOG] {}", format!($($arg)*));
};
}
macro_rules! warn {
($($arg:tt)+) => {
println!("[WARN] {}", format!($($arg)+));
};
}
fn main() {
log!("Hello, {}!", "world"); // 输出 [LOG] Hello, world!
warn!("Hello, {},{}!", "world", "lili"); // 输出 [WARN] Hello, world!
}
解引用
【示例】基础解引用
js
fn main() {
let x = 42;
let r = &x; // r 的类型是 &i32
// assert_eq! 是 Rust 标准库提供的一个断言宏,用于验证两个值是否相等
// 如果两个值不相等,程序会立即 panic 并显示详细的错误信息
assert_eq!(*r, 42); // *r 解引用得到 i32 值
}
assert!断言一个表达式为 trueassert!(x > 0)assert_eq!断言两个值相等assert_eq!(x, y)assert_ne!断言两个值不相等assert_ne!(x, y)
【实例】修改解引用
js
fn main() {
let mut x = 100;
let r = &mut x;
*r += 50; // 通过可变引用修改原值
println!("x = {}", x); // 输出 150
}
【示例】 智能指针Box<T>解引用
js
fn main() {
let b = Box::new(5);
assert_eq!(*b, 5); // Box 实现了 Deref,*b 调用 deref 返回 &i32,再解引用
let s = Box::new(String::from("hello"));
let owned = *s; // 从 Box 中移出 String(String 未实现 Copy)
// println!("{}", s); // 错误:s 已无效
println!("{}", owned); // 输出 hello
}
【示例】手动解引用
js
fn main() {
let x = Some(Box::new(10));
match x {
Some(b) => {
let value = *b; // 手动解引用 Box,得到 i32
println!("{}", value);
}
None => (),
}
}
在 Rust 中,Some 是 Option 枚举的一个变体 (variant),用于表示存在某个值的情况。
Option 枚举是 Rust 中处理可能为空值的安全方式,它有两个变体:
Some(T)- 表示存在一个值,包含一个类型为T的值None- 表示不存在值
【示例】
js
fn main() {
let x = &mut 42;
// 先解引用再加法,正确。
*x += 1;
println!("{}", x); // 43
}
【示例】链式解引用
js
use std::rc::Rc;
fn main() {
let rc = Rc::new(Box::new(99));
let value = **rc; // 第一次解引用 Rc -> Box,第二次 Box -> i32
println!("{}", value);
}