Rust: Rust 数组与元组:固定长度集合和异构元组

最后更新:2026-08-26

数组和元组是 Rust 中最基础的"固定大小集合"——数组存放相同类型的多个值,元组可以存放不同类型的多个值。它们都在上分配,没有运行时开销。

对于系统编程而言,固定大小集合意味着确定的内存布局和可预测的性能。Rust 编译器需要知道每个变量占用多少内存,而数组和元组的大小在编译时就完全确定。


1. 你将学到


2. 一个财务分析师的故事

(1) 痛苦:用零散变量存数据

Alice 是公司的财务分析师,需要存储 2026 年上半年的每月营收数据:

RUST
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 数组的解法

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) 概念对比

100%
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:数组声明、访问与遍历(难度 ⭐)

RUST
// ============================================
// 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);
}

输出:

TEXT 📖 仅展示
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:数组越界与安全检查(难度 ⭐⭐)

RUST
// ============================================
// 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
}

输出:

TEXT 📖 仅展示
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:元组的创建、解构与函数返回值(难度 ⭐⭐)

RUST
// ============================================
// 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);
}

输出:

TEXT 📖 仅展示
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:数组遍历与常用方法(难度 ⭐⭐)

RUST
// ============================================
// 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());
}

输出:

TEXT 📖 仅展示
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:综合练习——学生成绩统计(难度 ⭐⭐⭐)

RUST
// ============================================
// 综合示例:数组 + 元组 + 迭代器实战
// ============================================

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]);
}

输出:

TEXT 📖 仅展示
=== 学生成绩报告 ===
姓名         科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] 存科目平均分。


❓ 常见问题

Q 数组 [u8; 3] 和切片 &[u8] 有什么区别?
A 数组大小编译时固定,栈上分配;切片是数组的视图(指针+长度),可以指向数组的一部分。
Q 元组最大能有多少个元素?
A 理论上最多 12 个元素(标准库为元组实现了 traits 到 12 个元素)。但超过 4~5 个字段建议用结构体。
Q 为什么我的数组越界了编译器不报错,运行时才 panic?
A 编译器只能检查编译时可确定的越界(如 arr[100] 且数组长度也是常量)。动态索引(如 arr[i]i 来自用户输入)只能在运行时检查。
Q 元组和结构体都是"打包多个值",什么时候用哪个?
A 元组适合临时的、字段没有语义名的情况(如多返回值);结构体适合字段有明确含义、需要复用的数据。
Q [0; 5] 这种写法对所有类型都有效吗?
A 要求元素类型实现了 Copy trait。

📖 小节


📝 作业

  1. 难度 ⭐:声明一个长度为 7 的 [f64; 7] 数组,存入一周 7 天的最高气温(单位:摄氏度),用循环找出最高温和最低温。
  2. 难度 ⭐⭐:编写函数 fn stats(arr: &[i32]) -> (i32, i32, f64) 返回(最小值、最大值、平均值)。在 main 中用 [10, 3, 7, 1, 9, 4] 测试。
  3. 难度 ⭐⭐⭐:定义一个数组存储学生的分数 [85, 92, 78, 90, 88]。手动实现一个函数 fn rank_scores(scores: &[i32]) -> Vec<(usize, i32, &str)>,返回排序后的 (排名, 分数, 等级) 元组列表(等级规则:>=90 为 "A", >=80 为 "B", >=70 为 "C", 其余为 "D")。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏