复合类型:数组(Array)
本教程共 78 篇 · 第 12 篇 · 更新于 2026-08-08 · 约 6 分钟阅读
本节目标:掌握数组的声明与访问,理解它”长度固定、元素同类型”的特点,看清下标越界为何会在运行时崩溃,并知道何时该用数组、何时该用动态 Vec。
元组适合装”几个不同类型的值”。如果你要装”一批同类型的值”——比如一年 12 个月的温度、一个棋盘 64 格的状态——就该用数组了。
12-1 声明数组
数组写在方括号里,元素逗号分隔,类型是同类的:
fn main() {
let a = [1, 2, 3, 4, 5];
println!("第一个元素是 {}", a[0]);
}
这里 a 的类型是 [i32; 5]:方括号表示”数组”,i32 是元素类型,5 是长度。Rust 的数组长度写在类型里,是类型的一部分——[i32; 5] 和 [i32; 6] 是不同类型。
Note和元组不同,数组里所有元素必须是同一类型。想混类型就得用元组或后面讲的结构体/枚举。
12-2 显式标注类型与长度
类型标注写成 [元素类型; 长度]:
let a: [i32; 5] = [1, 2, 3, 4, 5];
这种写法在你希望强制某个长度或元素类型时有用,比如对接某个要求”恰好 3 个数”的接口。
12-3 快速初始化相同的值
如果想让数组每个元素都一样,用”值;长度”语法:
let zeros = [0; 5]; // [0, 0, 0, 0, 0]
let trues = [true; 3]; // [true, true, true]
意思是”5 个 0”或”3 个 true”。比手写一长串方便,也避免数错个数。
12-4 下标访问(从 0 开始)
访问数组元素用方括号加下标,下标从 0 开始:
let a = [10, 20, 30, 40, 50];
let first = a[0]; // 10
let third = a[2]; // 30
第 1 个元素是 a[0],第 3 个是 a[2]。这是几乎所有编程语言的统一约定,来自内存偏移的历史。
12-5 越界会直接崩溃
这是数组最容易出事的地方。如果你访问的下标超过了数组长度:
let a = [1, 2, 3];
let x = a[5]; // 越界!
别的语言可能静默返回垃圾值或负数,Rust 在运行时会直接 panic(崩溃)并终止程序,报错类似 index out of bounds。这是一个保护机制:与其让你拿到一块错误的内存继续乱来,不如立刻停下,避免更危险的安全问题。
Warning注意是”运行时”崩溃,不是编译时。编译器没法预知你运行时会传什么下标,所以越界检查留在运行时做。这意味着即便编译通过,错误的下标照样会让程序挂掉。循环访问数组时务必确认下标范围。
12-6 数组 vs 动态 Vec
你可能会问:长度固定也太死板了,我想随时加元素怎么办?答案是用 Vec(动态向量,读作 vector),它存在堆上、能增长缩小。Vec 属于”集合类型”,在进阶章节专门讲。
简单对比:
- 数组
[T; N]:长度编译期固定,存在栈上,速度快、开销小。适合”长度已知且不变”的数据(比如 RGB 三原色、一年 12 个月)。 - Vec
:长度运行时可变,存在堆上,灵活。适合”数量会变”的数据(比如用户列表)。
Tip经验法则:数据个数在写代码时就确定、且永远不变,用数组;个数会变,用 Vec。新手前期练习用数组足够,等学到集合章节再拥抱 Vec。
12-7 数组也能迭代(预告)
虽然这章先不展开循环,但提一句:数组常配合 for 循环逐个处理元素,比如把每个元素翻倍。具体写法下一大章”控制流”会讲。你现在只要知道数组能整体声明、按下标取、越界会崩。
12-8 写数组的两个高频错误
写数组时有两个错误值得提前说。第一是下标越界,前面讲过运行时会 panic,循环访问时务必确认下标范围。第二个更隐蔽:数组类型把长度写进了类型里。如果你写了函数参数是 [i32; 3],传 [1,2,3,4](长度 4)会编译报错,因为 [i32;3] 和 [i32;4] 是不同类型。这和 Python 里”列表长度随便”完全不同,一开始容易懵。
fn print_three(arr: [i32; 3]) {
println!("{} {} {}", arr[0], arr[1], arr[2]);
}
// print_three([1, 2, 3, 4]); // 错!长度不匹配
Warning需要”接收任意长度数组”时,别用固定长度数组做参数,改用切片
&[i32](用&arr[..]传入),或者后面学的动态Vec。固定长度数组只在”长度编译期就确定且不变”时才合适,比如表示 RGB 三原色、三维坐标。
12-9 更安全的访问:get 方法
访问数组除了直接用 a[i],还有一个更安全的法子:a.get(i),它返回 Option<&T>——下标合法就 Some(引用),越界就 None,绝不 panic:
let a = [10, 20, 30];
match a.get(5) {
Some(v) => println!("有值 {v}"),
None => println!("越界了,但没崩"),
}
当你”不确定下标是否合法”时,用 get 比中括号安全,因为它把”可能越界”变成类型层面的 Option,逼你处理 None。中括号 a[i] 则用于”我确定下标合法”的场景,追求极速。
Note
a.len()返回元素个数(usize),.iter()产生元素引用供for遍历。这几个是数组最常用的方法,配合get几乎能覆盖日常所有数组操作。下一章会讲到怎么把数组当参数传给函数。
12-10 数组与 Vec 的选择再议
再来明确一次数组与 Vec 的分工,因为这是新手高频困惑。数组 [T; N]:长度编译期固定、数据在栈上、访问极快、无额外开销;适合”个数已知且永不变”的数据,如一年 12 个月、RGB 三原色、三维坐标。Vec:长度运行期可变、数据在堆上、能 push 能 pop;适合”数量会变”的数据,如用户列表、逐行读入的文件内容。
一个实用信号:如果你写代码时就要想”这个数组到底多长”,而长度来自运行期输入,那它本该是 Vec。数组更适合”写死的长度”。两者都能被 for 遍历、都能用切片,接口高度一致,所以切换成本低。
Note栈和堆的区别在这里很直观:数组小、定长、随作用域自动进出栈;Vec 要往堆上动态申请,多了”管理容量”的少量开销。日常小程序这点开销可忽略,选语义正确的那个更重要。
12-11 小结
数组是”同类型、固定长度”的元素序列,类型写作 [T; N]。下标从 0 开始,越界在运行时 panic。需要变长时换 Vec。到这,标量(整数/浮点/布尔/字符)和两种复合类型(元组/数组)都讲完了。下一章进入”函数”,看看怎么把代码组织成可复用的块。