Rust: Rust 数组与元组:固定长度集合和异构元组
最后更新:2026-08-26
数组和元组是 Rust 中最基础的"固定大小集合"——数组存放相同类型的多个值,元组可以存放不同类型的多个值。它们都在栈上分配,没有运行时开销。
对于系统编程而言,固定大小集合意味着确定的内存布局和可预测的性能。Rust 编译器需要知道每个变量占用多少内存,而数组和元组的大小在编译时就完全确定。
1. 你将学到
- 定义和初始化数组
[T; N]语法 - 通过索引访问数组元素和越界检查
- 元组的创建与模式解构
- 用元组实现函数多返回值
- 数组的遍历方法(for 循环、iter、enumerate)
2. 一个财务分析师的故事
(1) 痛苦:用零散变量存数据
Alice 是公司的财务分析师,需要存储 2026 年上半年的每月营收数据:
let jan = 120_000; // Jan
let feb = 135_000; // Feb
let mar = 128_000; // Mar
let apr = 142_000; // Apr
let may = 150_000; // May
let jun = 165_000; // Jun
// Want to calculate half-year total?
let sum = jan + feb + mar + apr + may + jun; // Tedious and error-prone
6 个月的数据就已经很烦了——如果要存 365 天呢?写 365 个变量?函数的参数传递 6 个变量已经令人抓狂。更重要的是,你不能用循环来处理它们。
(2) Rust 数组的解法
fn main() {
// Half-year revenue: Jan ~ Jun 2026
let revenue: [i32; 6] = [120_000, 135_000, 128_000, 142_000, 150_000, 165_000];
// Total: one line
let sum: i32 = revenue.iter().sum();
println!("2026 H1 total revenue: {} yuan", sum);
// Average
let avg = sum as f64 / revenue.len() as f64;
println!("Monthly average: {:.0} yuan", avg);
// Print each month
for (i, val) in revenue.iter().enumerate() {
println!("Month {}: {} yuan", i + 1, val);
}
}
数组把所有同类型的数据放在一个变量里——可以用索引访问、可以用循环遍历、可以传给函数、可以用迭代器计算汇总。6 行代码替代了 20 行零散变量。
3. 数组与元组
(1) 概念对比
graph TB
A[Rust fixed-size collections] --> B[Array [T; N]]
A --> C[Tuple (T1, T2, ...)]
B --> D[All elements same type]
B --> E[Compile-time fixed length]
B --> F[Index access: arr[i]]
C --> G[Elements can differ in type]
C --> H[Pattern destructuring]
C --> I[Multiple return values]
(2) 数组 vs 元组
| 维度 | 数组 [T; N] |
元组 (T1, T2, ...) |
|---|---|---|
| 元素类型 | 必须全部相同 | 可以不同 |
| 长度 | 编译时固定(N) | 编译时固定(元素个数) |
| 访问方式 | arr[索引] |
tuple.字段号 或解构 |
| 适用场景 | 同类型数据集合(如月份营收) | 异构数据组合(如返回值和错误码) |
| 内存 | 连续内存块 | 连续内存块(可能含对齐填充) |
| 泛型参数 | [T; N] — 类型 + 长度 |
(T1, T2) — 每个位置类型 |
| 越界检查 | 运行时 panic | 不适用(字段号编译时已知) |
(3) 数组常用方法速查
| 方法 | 返回类型 | 说明 |
|---|---|---|
len() |
usize |
返回数组长度 |
get(i) |
Option<&T> |
安全索引访问 |
get_mut(i) |
Option<&mut T> |
安全可变访问 |
iter() |
Iter<T> |
返回引用迭代器 |
contains(&val) |
bool |
是否包含某值 |
sort() |
() |
原地排序(需 &mut) |
map(f) |
— | 通过 .iter().map() 转换 |
reverse() |
() |
原地反转(需 &mut) |
(4) 固定长度集合选型
| 场景 | 推荐类型 | 原因 |
|---|---|---|
| 同类型固定数量 | [T; N] 数组 |
栈分配、零开销、类型安全 |
| 异构固定组合 | (T1, T2, ...) 元组 |
不同类型组合、模式解构 |
| 函数多返回值 | 元组 | 轻量、无需定义结构体 |
| RGB/坐标 | 元组结构体 | 有类型名、防混用 |
| 大量同类型数据 | Vec<T> |
动态增长、堆分配 |
4. 数组与元组示例
▶ 示例 1:数组声明、访问与遍历(难度 ⭐)
// ============================================
// Arrays: declaration, indexing, and iteration
// ============================================
fn main() {
// Type 1: Explicit type annotation
let months: [i32; 6] = [100, 200, 300, 400, 500, 600];
// Type 2: Type inference
let zeros = [0; 5]; // [0, 0, 0, 0, 0], shorthand for [0, 0, 0, 0, 0]
// Access by index (0-based)
println!("First month: {}", months[0]); // 100
println!("Third month: {}", months[2]); // 300
// len() returns the array length
println!("Array length: {}", months.len()); // 6
// Iterate with a for loop
print!("All values: ");
for val in months {
print!("{} ", val);
}
println!();
// Iterate with index using .iter().enumerate()
for (index, value) in months.iter().enumerate() {
println!("months[{}] = {}", index, value);
}
// The shorthand `[val; N]` syntax
println!("Zeros array: {:?}", zeros);
}
输出:
First month: 100
Third month: 300
Array length: 6
All values: 100 200 300 400 500 600
months[0] = 100
months[1] = 200
months[2] = 300
months[3] = 400
months[4] = 500
months[5] = 600
Zeros array: [0, 0, 0, 0, 0]
数组的声明方式有两种:
[初始值; 长度]是方便的简写,[类型; 长度]是完整注解。通过arr[i]索引访问,通过for val in arr遍历。.iter().enumerate()帮你同时拿到索引和值。
▶ 示例 2:数组越界与安全检查(难度 ⭐⭐)
// ============================================
// Array out-of-bounds: Rust panics at runtime
// ============================================
fn main() {
let scores: [i32; 3] = [95, 87, 92];
// Safe access: within bounds
println!("scores[0] = {}", scores[0]); // OK
println!("scores[1] = {}", scores[1]); // OK
println!("scores[2] = {}", scores[2]); // OK
// Out of bounds: THIS WILL PANIC at runtime
// Uncomment the line below to see the error:
// println!("scores[5] = {}", scores[5]);
//
// Output:
// thread 'main' panicked at src/main.rs:XX:YY:
// index out of bounds: the len is 3 but the index is 5
// Safe alternative: use .get() which returns Option<&T>
let first = scores.get(0); // Some(&95)
let invalid = scores.get(5); // None
match first {
Some(val) => println!("Safe get(0): {}", val),
None => println!("Index 0 out of bounds"),
}
match invalid {
Some(val) => println!("Safe get(5): {}", val),
None => println!("Index 5 out of bounds -- safely handled!"),
}
// Using .get() with a default value
let val = scores.get(5).copied().unwrap_or(-1);
println!("scores.get(5) with default: {}", val); // -1
}
输出:
scores[0] = 95
scores[1] = 87
scores[2] = 92
Safe get(0): 95
Index 5 out of bounds -- safely handled!
scores.get(5) with default: -1
直接用
arr[i]越界会导致运行时 panic(崩溃)。安全的方式是用.get()方法——它返回Option<&T>,你可以用 match 或 unwrap_or 优雅处理越界情况,而不是直接崩溃。
▶ 示例 3:元组的创建、解构与函数返回值(难度 ⭐⭐)
// ============================================
// Tuples: creation, destructuring, and return values
// ============================================
// A function that returns a tuple: (sum, count, average)
fn analyze_sales(sales: &[i32]) -> (i32, usize, f64) {
let sum: i32 = sales.iter().sum();
let count = sales.len();
let avg = sum as f64 / count as f64;
(sum, count, avg) // return as a tuple
}
fn main() {
// Tuple with different types: (name, age, active)
let person: (&str, u8, bool) = ("Alice", 30, true);
// Access by field index
println!("Name: {}", person.0);
println!("Age: {}", person.1);
println!("Active: {}", person.2);
// Destructuring: unpack tuple into variables
let (name, age, active) = person;
println!("Destructured -- {} is {} years old, active: {}", name, age, active);
// Tuple as function return value
let q1_sales = [120_000, 135_000, 128_000]; // Jan, Feb, Mar
let (sum, count, avg) = analyze_sales(&q1_sales);
println!("Q1 -- Sum: {}, Count: {}, Avg: {:.0}", sum, count, avg);
// Nested tuples
let nested = (1, (2.5, "hello"), true);
println!("Nested: {:?}, inner: {}", nested, (nested.1).1);
// Single-element tuple: note the trailing comma!
let single = (42,); // tuple with one element
let not_tuple = (42); // just a parenthesized integer
println!("Single-element tuple: {:?}", single);
println!("Not a tuple: {}", not_tuple);
}
输出:
Name: Alice
Age: 30
Active: true
Destructured -- Alice is 30 years old, active: true
Q1 -- Sum: 383000, Count: 3, Avg: 127667
Nested: (1, (2.5, "hello"), true), inner: hello
Single-element tuple: (42,)
Not a tuple: 42
元组可以存不同类型的值,通过
.0、.1、.2访问字段,或者用let (a, b, c) = tuple模式解构。元组特别适合函数返回多个值——不需要定义结构体。注意单元素元组要加逗号(42,)。
▶ 示例 4:数组遍历与常用方法(难度 ⭐⭐)
// ============================================
// Array iteration and common methods
// ============================================
fn main() {
let revenue: [i32; 6] = [120_000, 135_000, 128_000, 142_000, 150_000, 165_000];
let month_names = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"];
// Method 1: for-in (by value -- copies each element for i32)
print!("All revenues: ");
for r in revenue {
print!("{} ", r);
}
println!();
// Method 2: .iter() (by reference)
print!("With .iter(): ");
for r in revenue.iter() {
print!("{} ", r);
}
println!();
// Method 3: .iter().enumerate() (index + value)
println!("\nMonthly report:");
for (i, r) in revenue.iter().enumerate() {
println!(" {}: {} yuan", month_names[i], r);
}
// Method 4: for i in 0..len (C-style index)
println!("\nGrowth rate:");
for i in 1..revenue.len() {
let growth = (revenue[i] - revenue[i - 1]) as f64 / revenue[i - 1] as f64 * 100.0;
println!(" {} -> {}: {:.1}%", month_names[i - 1], month_names[i], growth);
}
// Common array methods
println!("\nSummary:");
println!(" Total: {}", revenue.iter().sum::<i32>());
println!(" Max: {}", revenue.iter().max().unwrap());
println!(" Min: {}", revenue.iter().min().unwrap());
println!(" Count > 140k: {}", revenue.iter().filter(|&&r| r > 140_000).count());
}
输出:
All revenues: 120000 135000 128000 142000 150000 165000
With .iter(): 120000 135000 128000 142000 150000 165000
Monthly report:
Jan: 120000 yuan
Feb: 135000 yuan
Mar: 128000 yuan
Apr: 142000 yuan
May: 150000 yuan
Jun: 165000 yuan
Growth rate:
Jan -> Feb: 12.5%
Feb -> Mar: -5.2%
Mar -> Apr: 10.9%
Apr -> May: 5.6%
May -> Jun: 10.0%
Summary:
Total: 840000
Max: 165000
Min: 120000
Count > 140k: 3
数组遍历有多种方式:
for val in arr(复制值)、arr.iter()(引用)、.enumerate()(带索引)、C 风格下标循环。数组还提供了.sum()、.max()、.min()、.filter()等丰富的迭代器方法。
▶ 示例 5:综合练习——学生成绩统计(难度 ⭐⭐⭐)
// ============================================
// 综合示例:数组 + 元组 + 迭代器实战
// ============================================
fn analyze(scores: &[i32]) -> (i32, i32, f64, i32) {
let min = *scores.iter().min().unwrap_or(&0);
let max = *scores.iter().max().unwrap_or(&0);
let sum: i32 = scores.iter().sum();
let avg = if scores.is_empty() { 0.0 } else { sum as f64 / scores.len() as f64 };
let above_avg = scores.iter().filter(|&&s| s as f64 >= avg).count() as i32;
(min, max, avg, above_avg)
}
fn classify(score: i32) -> &'static str {
match score {
90..=100 => "A",
80..=89 => "B",
70..=79 => "C",
60..=69 => "D",
_ => "F",
}
}
fn main() {
let students = [
("Alice", [95, 88, 92]),
("Bob", [72, 65, 58]),
("Charlie", [85, 90, 78]),
("David", [60, 55, 70]),
];
println!("=== 学生成绩报告 ===");
println!("{:<10} {:>6} {:>6} {:>6} {:>8} {:>6}", "姓名", "科1", "科2", "科3", "平均", "等级");
println!("{}", "-".repeat(44));
let mut all_scores: Vec<i32> = Vec::new();
for (name, scores) in &students {
let (min, max, avg, above) = analyze(scores);
let grade = classify(avg as i32);
println!("{:<10} {:>6} {:>6} {:>6} {:>8.1} {:>6}",
name, scores[0], scores[1], scores[2], avg, grade);
all_scores.extend(scores.iter());
}
let (overall_min, overall_max, overall_avg, above_count) = analyze(&all_scores);
println!("{}", "-".repeat(44));
println!("全班统计: 最低={}, 最高={}, 平均={:.1}, >=平均: {}人",
overall_min, overall_max, overall_avg, above_count);
let subject_avgs: [f64; 3] = [
students.iter().map(|(_, s)| s[0] as f64).sum::<f64>() / students.len() as f64,
students.iter().map(|(_, s)| s[1] as f64).sum::<f64>() / students.len() as f64,
students.iter().map(|(_, s)| s[2] as f64).sum::<f64>() / students.len() as f64,
];
println!("\n科目平均: 科1={:.1}, 科2={:.1}, 科3={:.1}",
subject_avgs[0], subject_avgs[1], subject_avgs[2]);
}
输出:
=== 学生成绩报告 ===
姓名 科1 科2 科3 平均 等级
--------------------------------------------
Alice 95 88 92 91.7 A
Bob 72 65 58 65.0 D
Charlie 85 90 78 84.3 B
David 60 55 70 61.7 D
--------------------------------------------
全班统计: 最低=55, 最高=95, 平均=76.7, >=平均: 6人
科目平均: 科1=78.0, 科2=74.5, 科3=74.5
此示例综合运用了数组和元组:
[i32; 3]存每人 3 科成绩,元组(&str, [i32; 3])组合姓名和成绩,analyze返回(min, max, avg, count)元组,[f64; 3]存科目平均分。
❓ 常见问题
[u8; 3] 和切片 &[u8] 有什么区别?arr[100] 且数组长度也是常量)。动态索引(如 arr[i] 且 i 来自用户输入)只能在运行时检查。[0; 5] 这种写法对所有类型都有效吗?Copy trait。📖 小节
- 数组
[T; N]是同类型、固定长度的集合,编译时大小已知,栈上分配 - 数组通过
arr[i]索引访问,越界导致运行时 panic;.get()方法返回Option安全处理 - 数组简写
[val; N]创建 N 个相同值的元素(要求 T: Copy) - 元组
(T1, T2)可以存不同类型,通过.0/.1或模式解构访问 - 元组非常适合函数多返回值——
fn foo() -> (i32, String) - 元组 vs 结构体的选择:临时数据用元组,有含义的字段用结构体
📝 作业
- 难度 ⭐:声明一个长度为 7 的
[f64; 7]数组,存入一周 7 天的最高气温(单位:摄氏度),用循环找出最高温和最低温。 - 难度 ⭐⭐:编写函数
fn stats(arr: &[i32]) -> (i32, i32, f64)返回(最小值、最大值、平均值)。在 main 中用[10, 3, 7, 1, 9, 4]测试。 - 难度 ⭐⭐⭐:定义一个数组存储学生的分数
[85, 92, 78, 90, 88]。手动实现一个函数fn rank_scores(scores: &[i32]) -> Vec<(usize, i32, &str)>,返回排序后的 (排名, 分数, 等级) 元组列表(等级规则:>=90 为 "A", >=80 为 "B", >=70 为 "C", 其余为 "D")。