Rustの配列とタプル:固定長のコレクションと異種タプル
配列とタプルは、Rustにおける最も基本的な「固定サイズのコレクション」です。配列は同じ型の複数の値を格納するのに対し、タプルは異なる型の複数の値を格納できます。これらはどちらもスタック上に割り当てられ、実行時のオーバーヘッドは発生しません。
システムプログラミングにおいて、固定サイズのコレクションは、明確に定義されたメモリ配置と予測可能なパフォーマンスを意味します。Rustコンパイラは、各変数がどの程度のメモリを占有するかを把握する必要があり、配列やタプルのサイズはコンパイル時に完全に決定されます。
1. 学習内容
- 配列の定義と初期化の構文
[T; N] - インデックスによる配列要素へのアクセスと範囲外チェック
- タプルの作成とパターン展開
- タプルを使用して、複数の戻り値を持つ関数を実装する
- 配列を反復処理する方法(forループ、iter、enumerate)
2. ある金融アナリストの物語
(1) 問題点:異なる変数へのデータ格納
アリスは同社の財務アナリストで、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) 配列とタプル
| 次元 | 配列 [T; N] |
タプル (T1, T2, ...) |
|---|---|---|
| 要素の種類 | すべて同じでなければならない | 異なるものでもよい |
| 長さ | コンパイル時に固定 (N) | コンパイル時に固定 (要素数) |
| アクセス方法 | arr[index] |
tuple.field_index または脱構築 |
| ユースケース | 同じ種類のデータセット(例:月次売上高) | 異種のデータの組み合わせ(例:戻り値とエラーコード) |
| メモリ | 連続したメモリブロック | 連続したメモリブロック(アライメントパディングを含む場合あり) |
| 汎用パラメータ | [T; N] — 型 + 長さ |
(T1, T2) — 各位置の型 |
| 境界外チェック | 実行時パニック | 該当なし(フィールド番号はコンパイル時に判明している) |
(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]
配列を宣言するには2つの方法があります。
[initial_value; length]は便利な省略形であり、[type; length]は完全な構文です。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]を直接使用すると、インデックスが範囲外の場合に実行時のパニック(クラッシュ)が発生します。安全な方法は.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パターンを使ってデストラクチャリングを行うことができます。タプルは、構造体を定義する必要なく、複数の値を返す関数に特に適しています。なお、要素が1つのタプルはコンマ(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) ▶ サンプル:題 5:総合演習—学生の成績統計(難易度 ⭐⭐⭐)
// ============================================
// Comprehensive Example: Array + Tuple + Iterators in Practice
// ============================================
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!("=== Student Report Card ===");
println!("{:<10} {:>6} {:>6} {:>6} {:>8} {:>6}", "Name", "Sub1", "Sub2", "Sub3", "Average", "Level");
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!("Class-wide Statistics: Lowest={}, Highest={}, Average={:.1}, >=Average: {} people",
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!("\nSubject Average: Sub1={:.1}, Sub2={:.1}, Sub3={:.1}",
subject_avgs[0], subject_avgs[1], subject_avgs[2]);
}
出力:
=== Student Report Card ===
Name Sub1 Sub2 Sub3 Average Level
--------------------------------------------
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
--------------------------------------------
Class-wide Statistics: Lowest=55, Highest=95, Average=76.7, >=Average: 6 people
Subject Average: Sub1=78.0, Sub2=74.5, Sub3=74.5
この例では、配列とタプルの使い方を組み合わせています。
[i32; 3]は各生徒の 3 科目の得点を格納し、タプル(&str, [i32; 3])は名前と得点を組み合わせたものです。analyzeはタプル(min, max, avg, count)を返し、[f64; 3]は各科目の平均得点を格納します。
❓ よくある質問
[u8; 3] とスライス &[u8] の違いは何ですか?arr[100] など)に限られます。動的なインデックス指定(例えば、iがユーザー入力によるものであるarr[i]のような場合)は、実行時にしかチェックできません。[0; 5] この構文はすべての型で機能しますか?Copy トレイトを実装している必要があります。📖 まとめ
- 配列
[T; N]は、同じ型で固定長の要素の集合です。そのサイズはコンパイル時に判明しており、スタック上に割り当てられます。 - 配列へのアクセスは
arr[i]インデックスを介して行われます。インデックス範囲外のアクセスを行うと、実行時に パニック が発生します。.get()メソッドは、安全な処理のためにOptionを返します。 - 配列の省略記法
[val; N]同じ値を持つ N 個の要素を作成する(T: コピーが必要) - タプル
(T1, T2)は異なる型の値を格納することができ、.0/.1またはパターン分解を通じてアクセスできます。 - タプルは、複数の戻り値を持つ関数に最適です—
fn foo() -> (i32, String) - タプルと構造体の使い分け:一時的なデータにはタプルを、特定の意味を持つフィールドには構造体を使用する
📝 練習問題
- 難易度 ⭐:週の7日それぞれの最高気温(摂氏)を格納するために、長さ7の
[f64; 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」)。



