404 Not Found

404 Not Found


nginx

Rustの配列とタプル:固定長のコレクションと異種タプル

配列とタプルは、Rustにおける最も基本的な「固定サイズのコレクション」です。配列は同じ型の複数の値を格納するのに対し、タプルは異なる型の複数の値を格納できます。これらはどちらもスタック上に割り当てられ、実行時のオーバーヘッドは発生しません。

システムプログラミングにおいて、固定サイズのコレクションは、明確に定義されたメモリ配置と予測可能なパフォーマンスを意味します。Rustコンパイラは、各変数がどの程度のメモリを占有するかを把握する必要があり、配列やタプルのサイズはコンパイル時に完全に決定されます。


1. 学習内容


2. ある金融アナリストの物語

(1) 問題点:異なる変数へのデータ格納

アリスは同社の財務アナリストで、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) 配列とタプル

次元 配列 [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) ▶ サンプル:配列の宣言、アクセス、および反復処理(難易度 ⭐)

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]

配列を宣言するには2つの方法があります。[initial_value; length]は便利な省略形であり、[type; length]は完全な構文です。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] を直接使用すると、インデックスが範囲外の場合に実行時のパニック(クラッシュ)が発生します。安全な方法は .get() メソッドを使用することです。このメソッドは Option<&T> を返すため、即座にクラッシュするのではなく、matchunwrap_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 パターンを使ってデストラクチャリングを行うことができます。タプルは、構造体を定義する必要なく、複数の値を返す関数に特に適しています。なお、要素が1つのタプルはコンマ (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) ▶ サンプル:題 5:総合演習—学生の成績統計(難易度 ⭐⭐⭐)

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

出力:

TEXT
=== 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] は各科目の平均得点を格納します。


❓ よくある質問

Q 配列 [u8; 3] とスライス &[u8] の違いは何ですか?
A 配列のサイズはコンパイル時に固定され、スタック上に割り当てられます。一方、スライスは配列の一部を指すことができる、配列のビュー(ポインタ+長さ)です。
Q タプルの要素数は最大でいくつまでですか?
A 理論上は最大12個までです(標準ライブラリでは、最大12個の要素を持つタプルのトレイトが実装されています)。ただし、フィールドが4つや5つを超える場合は、構造体(struct)を使用することをお勧めします。
Q 配列の範囲外アクセスが発生してもコンパイラはエラーを報告しないのに、実行時にパニックが発生するのはなぜですか?
A コンパイラがチェックできるのは、コンパイル時に判定可能な範囲外アクセス(配列の長さも定数である arr[100] など)に限られます。動的なインデックス指定(例えば、iがユーザー入力によるものであるarr[i]のような場合)は、実行時にしかチェックできません。
Q タプルも構造体も「複数の値をまとめる」ために使われますが、それぞれをいつ使うべきでしょうか?
A タプルは、フィールドに意味のある名前がついていない一時的な状況(複数の戻り値など)に適しています。一方、構造体は、フィールドに明確な意味があり、再利用が必要なデータに適しています。
Q [0; 5] この構文はすべての型で機能しますか?
A 要素の型は Copy トレイトを実装している必要があります。

📖 まとめ


📝 練習問題

  1. 難易度 ⭐:週の7日それぞれの最高気温(摂氏)を格納するために、長さ7の[f64; 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%