你是不是也曾经疑惑过:
"为啥我在 Rust 里传个值,就被强行 move 掉了?"
"为啥我用了个引用,编译器又跟我谈什么生命周期?"
"GC 多香啊,Rust 这是跟自己过不去?"
作为一个前端出身的开发者,我们长期在 JS 的温柔乡里生活,内存管理这事从没操过心。GC 会帮我们自动清理变量,闭包里不小心残留的引用最多也就内存泄漏,不至于程序崩。但 Rust 是个狠角色:不给 GC 上场机会,自己就把内存收拾得明明白白。
这篇文章,我们就用接地气的方式把 Rust 的内存管理从头到尾讲清楚,适合你这种"JS/TS 写熟了、准备往系统编程 or WASM 拓展"的开发者入门。
一、值存在哪?Rust 的内存分布图
1. 虚拟内存空间:程序的内存五大区
在操作系统眼里,一个运行中的程序会被分成几块内存区域(图示一下会更清晰,这里先口述):
- 代码区(代码逻辑)
- 数据区(全局变量、静态变量)
- 栈(stack)📦:存放小而快的局部变量
- 堆(heap)🎒:存放需要动态分配、生命周期不确定的数据
- 保留区域(OS 管理)
Rust 是系统语言,直接跟这些内存区域打交道,不像 JS 隔着 VM。
2. 栈(stack)和堆(heap):你存哪我看着
这俩名字你可能在 JS 的 call stack 概念里听过,但在 Rust 里它们是真·内存空间。
特性 | 栈 stack | 堆 heap |
---|---|---|
存储 | 已知大小、作用域明确的值(如整数、布尔值、元组) | 不确定大小的值(如 String 、Vec ) |
分配速度 | 非常快(LIFO) | 慢(动态分配) |
是否需要手动释放 | 否,自动弹出 | 是,Rust 自动释放(靠所有权) |
是否有所有权规则 | ✅ 有 | ✅ 有 |
举个例子:
ini
let a = 42; // 存在栈上
let b = String::from("hi"); // `b` 是栈上的变量,但它指向堆上的内容
所以你可以理解成:Rust 会尽量把能放栈的都放栈,放不下的放堆上,然后由你写代码时的"所有权"逻辑来决定这块堆内存何时释放。
3. Rust 数据类型的内存归属
你写代码时常用的这些类型:
类型 | 存在哪 | Move/Copy 行为 |
---|---|---|
i32 , bool , char , f64 |
栈 | Copy (复制) |
String , Vec<T> , Box<T> |
堆(栈上指针 + 堆上数据) | Move (移动所有权) |
&T , &mut T |
栈(只是引用) | 不拥有内存 |
理解这一点非常关键:Rust 的值是分为"栈上元数据"和"堆上数据"的,只有栈上指针是默认可复制的,堆上内容的归属才是管理重点。
二、值的所有权:谁负责谁回收
Rust 的所有权系统,是它的看家本领。只要搞懂了这部分,内存安全就是你的囊中之物。
1. 栈内存的自动管理方案
csharp
fn main() {
let x = 5; // 栈上分配,出作用域自动回收
let y = x; // Copy,两个栈上变量,互不干扰
}
基本类型都是 Copy
,不涉及 move。
2. 所有权规则三条军规
- 每个值都有一个所有者变量
- 同时只能有一个所有者
- 当所有者离开作用域,值会被自动释放
csharp
fn main() {
let s1 = String::from("hello");
let s2 = s1; // 所有权转移,s1 无效
// println!("{}", s1); ❌ 报错:已被 move
}
3. 移动(move)和拷贝(copy)
Rust 把所有权"转移"叫 move;把值"复制"叫 copy。
默认只有实现了 Copy
trait 的类型(标量、数组、元组等)才能复制,否则都要 move。
你也可以用 .clone()
显式深拷贝堆上的数据:
css
let a = String::from("hi");
let b = a.clone(); // ✅ a 还能用,但 clone 成本高
三、引用和生命周期:借你一下,不归我
这部分是 Rust 的内存哲学巅峰之作了:
1. 只读 / 可变引用(borrow)
&T
是只读借用(可有多个)&mut T
是可变借用(只能一个)
ini
let mut s = String::from("Hi");
let r1 = &s;
let r2 = &s;
// let r3 = &mut s; ❌ 报错:不能同时有可变引用
let r4 = &mut s; // ✅ 这是唯一可变引用
这套规则称为**"借用检查器 Borrow Checker"**,你编译都过不了,根本不会出现 JS 里的"同时读写"的奇葩 bug。
2. 引用规则
- 借用不能超出被引用对象的生命周期
- 所有引用必须有效、不会悬空
- 可变借用不能与其他借用同时存在
3. 生命周期与引用有效性
生命周期用 'a
形式表示,目的是告诉编译器:这个引用值的"有效时长"是和哪个变量绑定的。
rust
fn longer<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
你写泛型时加个 <T>
,写生命周期时就加个 <'a>
,也类似泛型。
四、智能指针:更灵活的内存管理
Rust 提供了几种更高级的"托管内存"指针工具,它们让你在 共享状态、跨作用域、封装可变性 时更游刃有余:
1. Box<T>
:堆上存值,适合大对象/递归结构
- 把东西从栈"搬到堆上"
- 所有权依旧独占,只不过堆上存数据
css
let b = Box::new(5); // 数据存在堆上,但 b 拥有它
适合处理大小不固定、嵌套层次不明的场景(如链表、树)。
2. Rc<T>
:引用计数,多个不可变共享所有权
- 允许多个变量共享同一堆内存
- 适用于单线程环境
- 每次
.clone()
会增加引用计数
ini
use std::rc::Rc;
let a = Rc::new(String::from("Hi"));
let b = a.clone();
let c = a.clone();
适合状态共享场景(比如 React 的 context-like 模型)。
3. RefCell<T>
:内部可变性,运行时检查可变借用
- 编译期允许多个不可变引用,但内部实现了运行时的 borrow check
- 你可以在不可变结构里修改内容
ini
use std::cell::RefCell;
let data = RefCell::new(42);
*data.borrow_mut() += 1;
适合构建类 JS Proxy 的结构,允许封装可变行为。
五、不安全指针:逃脱 Rust 管理的"野路子"
Rust 支持写 unsafe 代码块,可以用原始指针 *const T
/ *mut T
,但:
- 没有 borrow check
- 没有生命周期检查
- 容易炸
一般用于和底层 C 交互、性能极限优化、FFI、内核开发。
ini
unsafe {
let ptr = some_raw_pointer;
*ptr = 42;
}
一句话总结:能不用 unsafe 就别用,真用就请戴好头盔。
🎁 总结:Rust 内存管理全景图

Rust 通过"所有权 + 借用 + 生命周期"三件套,用编译时手段把内存问题掐死在摇篮里,既不要 GC,又让你写不出错。
✅ 给前端的建议路线
目标 | 推荐学习内容 |
---|---|
想用 Rust 写工具 | 学习所有权 + move + 引用 |
想用 Rust 写 WASM | 加深生命周期理解 + wasm-bindgen |
想写后端服务 | 学习智能指针 + 多线程并发模型 |
想和 JS 联动 | 学习 wasm-pack 和 js-sys 库 |