Rust 元组与数组内存管理笔记
主题 :元组 (Tuple) vs 定长数组
[T; N]的核心区别,以及数组在栈/堆的分配场景范围:类型约束、访问方式、内存布局、堆分配场景
目录
- 元组 vs 定长数组核心区别(#一 - 元组-vs-定长数组核心区别)
- [1.1 类型约束](#1.1 类型约束 "#11-%E7%B1%BB%E5%9E%8B%E7%BA%A6%E6%9D%9F")
- [1.2 访问方式](#1.2 访问方式 "#12-%E8%AE%BF%E9%97%AE%E6%96%B9%E5%BC%8F")
- [1.3 内存布局](#1.3 内存布局 "#13-%E5%86%85%E5%AD%98%E5%B8%83%E5%B1%80")
- [1.4 适用场景](#1.4 适用场景 "#14-%E9%80%82%E7%94%A8%E5%9C%BA%E6%99%AF")
- 数组栈/堆分配四种形态(#二 - 数组栈堆分配四种形态)
- 快速总结记忆(#三 - 快速总结记忆)
- 定长数组堆分配完整指南(#四 - 定长数组堆分配完整指南)
- 两种堆数组简单区分(#五 - 两种堆数组简单区分)
一、元组 vs 定长数组核心区别
1.1 类型约束
| 类型 | 约束说明 | 示例 |
|---|---|---|
数组 [T; N] |
所有元素同一种类型,长度 N 编译期固定 | let arr: [i32; 3] = [1, 2, 3]; |
元组 (T1, T2, ...) |
每个元素类型可以不一样,长度写在类型上 | let tup: (i32, f64, &str) = (10, 3.14, "abc"); |
rust
// 数组:全 i32,长度 3 固定
let arr: [i32; 3] = [1, 2, 3];
// 元组:不同类型混合
let tup: (i32, f64, &str) = (10, 3.14, "abc");
1.2 访问方式
| 类型 | 访问方式 | 遍历支持 |
|---|---|---|
| 数组 | arr[0] 下标数字访问 |
✅ 支持 for 循环 |
| 元组 | tup.0 点 + 数字 |
❌ 不能用 []、不能直接 for 遍历 |
1.3 内存布局
两者都是值类型,元素平铺连续存栈,内部不存指针:
- 数组:同类型连续紧密排列
- 元组:按成员顺序依次排列,不同类型占用宽度不同
都属于纯栈值类型,默认全部放在栈上。
1.4 适用场景
- 数组:一组同类型数据(坐标数组、字节缓冲区)
- 元组:轻量多类型复合结构(返回多个不同类型返回值)
二、数组栈/堆分配四种形态
分 4 种数组相关形态,一眼分清:
1. 裸定长数组 [T; N] → 全部在栈
编译期尺寸固定,整体平铺栈,无堆、无指针。
rust
let buf: [u8; 256] = [0; 256]; // 256 字节全部栈上
⚠️ 注意:数组太大超过栈容量会栈溢出,这时要用堆版本。
2. Box<[T]> → 栈存指针,数组本体在堆
Box 本身是栈上一根指针,真正的数组分配在堆。
rust
let heap_arr: Box<[i32]> = Box::new([1, 2, 3]);
// heap_arr 变量在栈,只存指针;[1,2,3] 在堆
3. 动态数组 Vec<T> → 栈存三元组,元素数组在堆
Vec 栈上存:堆指针 + len + cap;底层数组在堆,可动态扩容。
rust
let v = vec![1, 2, 3];
// 栈:(ptr, len, cap)
// 堆:存放 [1,2,3] 底层数组
4. 切片引用 &[T] / &mut [T] → 栈存胖指针,数组本体随便在哪
切片只是借用,不拥有内存;底层数组可以在栈 / 堆 / static。
rust
let arr = [1, 2, 3]; // 栈数组
let s = &arr[..]; // s 是栈上胖指针,指向栈上数组
let v = vec![1, 2]; // 堆数组
let s2 = &v[..]; // s2 胖指针指向堆数组
三、快速总结记忆
- 元组 &
[T;N]:原生裸形态全是栈值,无堆 - 想把数组放堆:用
Box<[T]>(固定长度堆数组)或Vec<T>(可变长度堆数组) &[T]只是指针借用,不决定数组在哪- 区分元组/数组最简单:元素类型是否统一,统一是数组,混杂是元组
四、定长数组堆分配完整指南
核心前提 :裸
[T; N]默认一定在栈,只有被所有权容器包裹时,数组本体才会放到堆。
4.1 手动用 Box 包裹(最常用、主动堆分配)
Box<[T; N]> 保留定长数组类型
Box::new([T;N]) 会把完整定长数组拷贝到堆,栈只存一根指针。
rust
// 数组本体在堆,big_arr 变量栈存指针
let big_arr: Box<[u8; 1024 * 1024]> = Box::new([0u8; 1024 * 1024]);
⚠️ 坑 :
Box::new([...])会先在栈创建完整数组再拷贝到堆 ,超大数组会栈溢出;超大数组推荐vec![]转 Box。
Box<[T]> 自动降级为切片(丢失长度常量 N)
堆分配定长数组,但类型变成不定长切片 [T],编译期不再保留固定长度:
rust
let arr_box: Box<[i32]> = Box::new([1, 2, 3]);
4.2 通过 Vec 转换堆分配(无栈临时拷贝,适合超大数组)
Vec 底层数组天生在堆,转成 Box 后数组留在堆:
rust
// 全程堆分配,不会临时占用大栈空间
let vec = vec![0u8; 10_000_000];
let heap_arr: Box<[u8; 10_000_000]> = vec.try_into().unwrap();
4.3 放在递归结构体/枚举内部(编译强制堆分配)
Rust 要求递归类型尺寸有限,必须用 Box 包内部定长数组,数组本体进堆:
rust
// 递归枚举,内部数组必须堆
enum Node {
Data([u8; 256]),
Next(Box<Node>),
}
// 修正:数组包 Box 放堆
enum NodeSafe {
Data(Box<[u8; 256]>),
Next(Box<NodeSafe>),
}
4.4 被共享智能指针包裹(Rc/Arc)
Rc<[T;N]> / Arc<[T;N]>:数组本体存堆,栈只存共享指针:
rust
use std::sync::Arc;
let arc_arr = Arc::new([0f64; 512]); // 数组堆上
4.5 被锁/内部可变性容器包裹(Mutex/RwLock/RefCell)
容器本身栈存指针,内部包裹的定长数组存堆:
rust
use std::sync::Mutex;
// [u32;1024] 本体在堆
let lock_arr = Mutex::new(Box::new([0u32; 1024]));
4.6 容易混淆:哪些不算数组本体堆分配
下面只是借用胖指针,数组本体仍在栈,不属于"数组分配到堆":
1. 切片引用 &[T] / &mut [T]
rust
let stack_arr = [1, 2, 3]; // 栈
let s = &stack_arr[..]; // s 栈存指针,数组还在栈
2. 函数返回裸 [T;N]
编译器 RVO 优化也只是栈拷贝,本体不进堆。
4.7 一句话快速判断口诀
- 没套
Box/Rc/Arc/Mutex→ 定长数组全在栈 - 外层包
Box/Rc/Arc所有权容器 → 数组本体堆上 - 只是
&[T]切片借用 → 数组在哪不变,仅指针放栈
五、两种堆数组简单区分
| 类型 | 特点 |
|---|---|
Box<[T; N]> |
保留编译期固定长度 N,类型是定长数组 |
Box<[T]> |
丢失常量长度,变成动态切片,只能运行时读 len |