カテゴリ: Rust 更新日: 2026/04/10

Rustのfor構文とイテレータを徹底解説!繰り返し処理とメモリ安全なループの仕組み

Rustのfor構文とイテレータの基本
Rustのfor構文とイテレータの基本

先生と生徒の会話形式で理解しよう

生徒

「Rustで配列の中身を順番に表示させたいんですが、forループの使い方が他の言語と少し違う気がします。イテレータって何ですか?」

先生

「鋭いですね。Rustのfor構文は、実はイテレータ(Iterator)という仕組みと深く結びついています。単に繰り返すだけでなく、メモリの安全性を守りながら効率よくデータを処理するための工夫が詰まっているんですよ。」

生徒

「メモリの安全性も関係しているんですね。具体的にどう書けば安全にループを回せるんでしょうか?」

先生

「Rustではインデックスによるアクセスよりも、イテレータを使う方が推奨されます。範囲指定やコレクションの反復処理など、基本的なパターンから応用まで順番に解説していきますね!」

1. Rustにおける制御構文の基本とループの重要性

1. Rustにおける制御構文の基本とループの重要性
1. Rustにおける制御構文の基本とループの重要性

プログラミングにおいて、特定の処理を何度も繰り返す「ループ処理」は欠かせない要素です。Rustには、条件が満たされるまで繰り返すwhile、無限ループを作るloop、そして最も多用されるforの三種類の制御構文が存在します。特にfor構文は、配列やベクトルといったデータの集まり(コレクション)を安全に走査するために設計されています。

他の言語、例えばC言語などでは「変数 i を 0 から 10 まで増やしていく」という書き方が一般的ですが、Rustではこの「数値の管理」を手動で行うことを極力避けます。なぜなら、手動で添え字(インデックス)を管理すると、範囲外アクセスによるクラッシュやセキュリティホールの原因になるからです。Rustのfor構文は、次に説明する「イテレータ」という仕組みを内部で利用することで、これらのリスクをコンパイルレベルで排除しています。

2. イテレータの概念とRustが安全な理由

2. イテレータの概念とRustが安全な理由
2. イテレータの概念とRustが安全な理由

イテレータ(Iterator)とは、一言で言えば「集合の中にある要素を順番に取り出す仕組み」のことです。Rustの標準ライブラリにはIteratorトレイトが用意されており、これに基づいたオブジェクトは「次の要素をください」という要求に応えることができます。

Rustのforループが安全だと言われる最大の理由は、イテレータが「境界チェック」の負担を減らしてくれる点にあります。配列のサイズをプログラマが意識しなくても、イテレータが自動的に最後の要素までを把握し、終わればループを終了させてくれます。これにより、メモリの安全性(メモリセーフ)が保たれ、バグの混入を防ぐことができるのです。また、イテレータは「遅延評価」という特性を持っており、必要になるまで計算を行わないため、パフォーマンス面でも非常に優れています。

3. 範囲指定を使った基本的なforループの使い方

3. 範囲指定を使った基本的なforループの使い方
3. 範囲指定を使った基本的なforループの使い方

まずは、指定した回数だけ処理を繰り返す最もシンプルな方法を見てみましょう。Rustではstart..endという範囲指定(Range)記法を使います。


fn main() {
    // 1から4まで(5は含まない)繰り返す
    for number in 1..5 {
        println!("カウントアップ: {}", number);
    }

    println!("---");

    // 1から5まで(5を含む)繰り返す
    for number in 1..=5 {
        println!("5まで数えるよ: {}", number);
    }
}

カウントアップ: 1
カウントアップ: 2
カウントアップ: 3
カウントアップ: 4
---
5まで数えるよ: 1
5まで数えるよ: 2
5まで数えるよ: 3
5まで数えるよ: 4
5まで数えるよ: 5

上記のコードでは、1..5という記法が自動的にイテレータとして振る舞います。末尾の数値を含めたい場合は、..=という記号を使います。非常に直感的で、数値のインクリメント(加算)を書き忘れて無限ループに陥る心配もありません。初心者が最初に覚えるべき、基本のループパターンです。

4. 配列やベクタとイテレータの連携

4. 配列やベクタとイテレータの連携
4. 配列やベクタとイテレータの連携

次に、実際の開発でよく使われる「データのリスト」を処理する方法を学びましょう。Rustの配列やVec(可変長配列)から要素を取り出す際は、直接forに渡すことができます。しかし、ここで重要になるのが「所有権」の概念です。通常、コレクションをそのままforに入れるとそのデータの所有権が移動してしまいますが、多くの場合は参照(借用)を使って読み取り専用で処理します。


fn main() {
    let fruits = vec!["リンゴ", "バナナ", "オレンジ"];

    // iter() メソッドを使って要素への参照を取得する
    for fruit in fruits.iter() {
        println!("大好きな果物: {}", fruit);
    }

    // 元のリストはまだ使える(所有権が移動していないため)
    println!("果物の数は全部で{}個です。", fruits.len());
}

大好きな果物: リンゴ
大好きな果物: バナナ
大好きな果物: オレンジ
果物の数は全部で3個です。

iter()メソッドを使うことで、リストの各要素に対する「読み取り権限」だけを借りてループを回せます。もし、ループ内で要素の中身を書き換えたい場合は、後述するiter_mut()を使用します。このように、Rustではループの回し方ひとつとっても、そのデータが「読み取り専用なのか」「書き込み可能なのか」「使い捨てなのか」を明確に区別します。

5. 要素を書き換えるための可変イテレータ

5. 要素を書き換えるための可変イテレータ
5. 要素を書き換えるための可変イテレータ

ループの中で配列の内容を変更したい場面もあります。その場合は、&mut(可変参照)を生成するiter_mut()を使用します。これにより、メモリの安全性を保ったまま、元のデータを直接操作することが可能です。


fn main() {
    let mut numbers = vec![1, 2, 3, 4, 5];

    // 各要素を2倍にする
    for num in numbers.iter_mut() {
        *num *= 2; // デリファレンス(*)して値を書き換える
    }

    println!("2倍になった配列: {:?}", numbers);
}

2倍になった配列: [2, 4, 6, 8, 10]

この例では、numは要素そのものではなく、要素への「変更可能な参照」です。そのため、*numと書いて元の場所にアクセスし、値を更新しています。Rustの厳格な借用規則により、このループが動いている間は他の場所からnumbersを書き換えることができないため、並行処理などでも安全性が担保されます。データ競合を未然に防ぐこの仕組みこそが、Rustの真骨頂です。

6. ステップ実行と逆順での繰り返し処理

6. ステップ実行と逆順での繰り返し処理
6. ステップ実行と逆順での繰り返し処理

時には、一つ飛ばしで処理したり、後ろから順番に処理したいこともあるでしょう。Rustのイテレータには、こうした要望に応えるための便利なメソッドが多数用意されています。代表的なものがstep_byrevです。


fn main() {
    println!("逆順でカウントダウン:");
    // rev() で範囲を反転させる
    for i in (1..=3).rev() {
        println!("{}!", i);
    }

    println!("2つずつ飛ばして表示:");
    // step_by(2) で2ステップずつ進む
    for i in (0..10).step_by(2) {
        println!("偶数: {}", i);
    }
}

逆順でカウントダウン:
3!
2!
1!
2つずつ飛ばして表示:
偶数: 0
偶数: 2
偶数: 4
偶数: 6
偶数: 8

これらは「アダプタ」と呼ばれ、元のイテレータを加工して新しい動作を定義します。複雑な条件をループの内部(if文など)で書くのではなく、イテレータの段階で定義することで、コードの見通しが非常に良くなります。これも可読性と保守性を高めるためのRustらしい書き方と言えます。

7. enumerateを使ったインデックス付きのループ

7. enumerateを使ったインデックス付きのループ
7. enumerateを使ったインデックス付きのループ

「今何回目のループか」を知るために、昔ながらのカウンタ変数を用意する必要はありません。enumerate()メソッドを使えば、要素と一緒に現在のインデックス(0から始まる番号)をペアで受け取ることができます。これは、リストの内容を表示する際に番号を振りたいときなどに非常に便利です。


fn main() {
    let languages = vec!["Rust", "Python", "Go", "TypeScript"];

    // (インデックス, 要素) のタプルで受け取る
    for (i, lang) in languages.iter().enumerate() {
        println!("第{}位: {}言語", i + 1, lang);
    }
}

第1位: Rust言語
第2位: Python言語
第3位: Go言語
第4位: TypeScript言語

タプル展開という機能を使って(i, lang)と書くことで、番号と値をスマートに分離して受け取っています。手動でi += 1と書く必要がないため、ミスの入り込む余地がありません。Rustにおける効率的で安全なコーディングスタイルの好例です。

8. mapやfilterによる関数型プログラミング的な処理

8. mapやfilterによる関数型プログラミング的な処理
8. mapやfilterによる関数型プログラミング的な処理

Rustのイテレータは、forループで使うだけでなく、メソッドを繋げていくことで複雑なデータ変換を行うことも得意です。例えば、リストの中から特定の条件に合うものだけを抽出し(filter)、それらを加工(map)して新しいリストを作る、といった処理を流れるように記述できます。

これは「関数型プログラミング」のスタイルですが、Rustではこれがゼロコスト抽象化、つまり手動でループを書くのと同等の速度で動作するように最適化されています。初心者のうちは少し難しく感じるかもしれませんが、for構文とイテレータの関係に慣れてくると、このメソッドチェーンの便利さが病みつきになるはずです。データの変換処理において、一時的な変数を減らすことができるため、副作用の少ない綺麗なコードになります。

9. 初心者が陥りやすいイテレータの注意点

9. 初心者が陥りやすいイテレータの注意点
9. 初心者が陥りやすいイテレータの注意点

Rustの学習を始めたばかりの人が最初につまずくのは、やはり「所有権」に関連するエラーです。例えば、into_iter()というメソッドを使うと、コレクションそのものを消費してしまい、ループの後にその変数を使うことができなくなります。これは「値を移動(ムーブ)」させるためです。

一方で、今回メインで紹介したiter()&arrという書き方は「借用」であり、所有権を維持したまま中身を覗き見ることができます。エラーメッセージで「value borrowed here after move(移動後に借用されています)」といった内容が出たときは、自分がイテレータをどう生成したかを確認してみてください。適切なメソッドを選ぶことが、Rustを乗りこなす第一歩です。

カテゴリの一覧へ
新着記事
New1
C言語
C言語のデバッグ環境を整える【GDB入門】初心者でもエラーの原因がわかる!
New2
C++
C++のヘッダファイルとソースファイル分割構成の完全ガイド!初心者でも理解できる
New3
C++
C++の可変長引数を完全解説!初心者でもわかる関数の引数を増やす方法
New4
C言語
C言語のCI/CD導入ガイド!GitHub Actionsで自動ビルドする方法
人気記事
No.1
Java&Spring記事人気No1
C言語
C言語を学ぶ初心者におすすめの環境構築手順【2025年版】
No.2
Java&Spring記事人気No2
C言語
Visual Studio CodeでC言語を実行する方法【拡張機能の設定と実行手順】
No.3
Java&Spring記事人気No3
C言語
LinuxでC言語開発環境を構築する方法【GCCとMakefileの基本】
No.4
Java&Spring記事人気No4
C言語
C言語開発でよく使われるエディタとIDEランキング【初心者向け完全ガイド】
No.5
Java&Spring記事人気No5
C言語
C言語のソースコードとヘッダファイルの役割とは?初心者向けにわかりやすく解説!
No.6
Java&Spring記事人気No6
C言語
C言語の配列と文字列の基本を完全ガイド!初心者でもわかる宣言と使い方
No.7
Java&Spring記事人気No7
C++
MinGWとMSYS2でWindowsにC++環境を構築する方法を徹底解説!初心者でもできるセットアップガイド
No.8
Java&Spring記事人気No8
C++
Makefileの基本構文とターゲット定義を初心者向けに解説