TS使っている人のRust0から勉強日記3
こんにちは!普段フロントエンド開発をしているまるです🙌
Rustの日記2を書いて一年経ってました….
今回はRustの配列やコレクションをループする仕組みであるiter()とfor文についてです。
そもそも配列とコレクションの違い
上の内容紹介のところで出てきた「配列」と「コレクション」とは何が違うのかについて説明します。
| 名前 | Rustでの型 | サイズの性質 | 使う場面 | メモリ |
|---|---|---|---|---|
| 配列(array) | [T;N] | 固定サイズ | 要素数が変わらない時 | スタック領域 |
| コレクション(collection) | Vec<T>など | 可変サイズ | 要素を増やしたり減らしたりする時 | ヒープ領域 |
スタック領域とヒープ領域の特徴
| 項目 | スタック | ヒープ |
|---|---|---|
| サイズ | 小さい(固定) | 大きい(可変) |
| 確保・解放 | 自動(関数の呼び出しにより) | 所有権やBoxで制御 |
| アクセス速度 | 高速 | やや遅い(ポインタを経由) |
| メモリ管理 | 自動 | 所有権により自動 |
Rustの配列とは、「サイズが決まっていて、変えられないもの」です。
let nums = [1,2,3];
Typescriptで言うと、下のように型定義で固定の要素の数のみで指定されたもののことですね。
const nums:[number,number,number] = [1,2,3]
Rustのコレクションとは、標準ライブラリで提供される「可変データ構造」の総称です。
よく使われるのはVec<T>やStringなど
let mut numbers = Vec::new();
numbers.push(1);
numbers.push(2);
numbers.push(3);
println!("{:?}", numbers); // [1, 2, 3]
Typescriptで言うと、
const numbers: number[] = [];
numbers.push(1);
numbers.push(2);
numbers.push(3);
console.log(numbers); // [1, 2, 3]
iter()の使い方
iter()
iter()とは「コレクションの要素を順番に取り出せるイテレーター(仕組み)を作る」というメゾットです。
配列やコレクションの要素に対して何かしらの処理を行いたい時に使います。
代表的なメソッド一覧
| メソッド | 説明 | 返り値 |
|---|---|---|
map |
各要素に関数を適用 | 新しいイテレーター |
filter |
条件を満たす要素だけ残す | 新しいイテレーター |
collect |
イテレーターをコレクションに変換 | コレクション(Vec, HashMapなど) |
sum |
要素の合計を計算 | 値(合計) |
product |
要素の積を計算 | 値(積) |
enumerate |
インデックス付きのタプルにする | 新しいイテレーター |
take |
先頭n個の要素を取得 | 新しいイテレーター |
skip |
先頭n個を飛ばす | 新しいイテレーター |
find |
条件に合う最初の要素を取得 | Option<&T> |
all |
全ての要素が条件を満たすか | bool |
any |
どれか1つでも条件を満たすか | bool |
max |
最大値を取得 | Option<&T> |
min |
最小値を取得 | Option<&T> |
chain |
他のイテレーターと連結 | 新しいイテレーター |
zip |
2つのイテレーターを組にする | 新しいイテレーター(タプル) |
flat_map |
イテレーターを平坦化して別のイテレーターに変換 | 新しいイテレーター |
peekable |
次の要素を覗き見可能にする | Peekableイテレーター |
rev |
逆順にイテレーション | 新しいイテレーター |
for_each |
各要素に関数を適用(副作用用) | ()(値は返さない) |
mapの使い方を例とします。
let numbers = vec![1, 2, 3, 4, 5];
let doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect(); //NG
let doubled: Vec<i32> = numbers.iter().map(|x| *x * 2).collect();
// または
let doubled: Vec<i32> = numbers.iter().cloned().map(|x| x * 2).collect();
// または
let doubled: Vec<i32> = numbers.iter().copied().map(|x| x * 2).collect();
その他のメソッド一覧のコード例
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let other_numbers = vec![6, 7, 8];
// filter: 偶数だけを取り出して新しいベクタにする(clonedで値にする)
let even: Vec<i32> = numbers.iter().filter(|x| **x % 2 == 0).cloned().collect();
println!("filter: {:?}", even); // [2, 4]
// collect: 各要素を2乗して新しいベクタにする(copiedで値にする例)
let squared: Vec<i32> = numbers.iter().copied().map(|x| x * x).collect();
println!("collect: {:?}", squared); // [1, 4, 9, 16, 25]
// sum: 合計を計算(copiedで値にして合計)
let total: i32 = numbers.iter().copied().sum();
println!("sum: {}", total); // 15
// product: 積を計算(copiedで値にして積)
let prod: i32 = numbers.iter().copied().product();
println!("product: {}", prod); // 120
// enumerate: インデックスと値を表示
for (i, x) in numbers.iter().enumerate() {
println!("enumerate: {} -> {}", i, x);
}
// take: 先頭3つだけ取り出す(clonedで値にする)
let first_three: Vec<i32> = numbers.iter().take(3).cloned().collect();
println!("take: {:?}", first_three); // [1, 2, 3]
// skip: 先頭2つを飛ばして残りを取り出す(clonedで値にする)
let after_two: Vec<i32> = numbers.iter().skip(2).cloned().collect();
println!("skip: {:?}", after_two); // [3, 4, 5]
// find: 3より大きい最初の要素を探す(**xで値にする)
if let Some(x) = numbers.iter().find(|x| **x > 3) {
println!("find: {}", x); // 4
}
// all: 全て正の数か?
let all_positive = numbers.iter().all(|x| *x > 0);
println!("all: {}", all_positive); // true
// any: 偶数が1つでもあるか?
let any_even = numbers.iter().any(|x| *x % 2 == 0);
println!("any: {}", any_even); // true
// max: 最大値(copiedで値にしてmax)
if let Some(max_val) = numbers.iter().copied().max() {
println!("max: {}", max_val); // 5
}
// min: 最小値(copiedで値にしてmin)
if let Some(min_val) = numbers.iter().copied().min() {
println!("min: {}", min_val); // 1
}
// chain: 2つのベクタをつなげて1つのベクタにする(clonedで値にする)
let combined: Vec<i32> = numbers.iter().chain(other_numbers.iter()).cloned().collect();
println!("chain: {:?}", combined); // [1, 2, 3, 4, 5, 6, 7, 8]
// zip: 数字と文字をペアで表示
let letters = vec!['a', 'b', 'c', 'd', 'e'];
for (num, letter) in numbers.iter().zip(letters.iter()) {
println!("zip: {} -> {}", num, letter);
}
// flat_map: ネストしたベクタを1つにまとめる(clonedで値にする)
let nested = vec![vec![1, 2], vec![3, 4], vec![5]];
let flat: Vec<i32> = nested.iter().flat_map(|v| v.iter()).cloned().collect();
println!("flat_map: {:?}", flat); // [1, 2, 3, 4, 5]
// peekable: 次の要素を覗き見できる
let mut iter = numbers.iter().peekable();
while let Some(x) = iter.next() {
if let Some(next_val) = iter.peek() {
println!("peekable: current: {}, next: {}", x, next_val);
} else {
println!("peekable: current: {}, next: None", x);
}
}
// rev: 逆順に並べる(clonedで値にする)
let reversed: Vec<i32> = numbers.iter().rev().cloned().collect();
println!("rev: {:?}", reversed); // [5, 4, 3, 2, 1]
// for_each: すべての要素を表示
numbers.iter().for_each(|x| println!("for_each: {}", x));
}
最初の例のNGのもの以外、map内の要素に*がついていたり、cloned()やcopy()が書かれていますが、これは所有権が関係してきます。一行目のNGの例を順番に見ていきましょう!

① numbers.iter()は&i32のイテレーターを返します。
= iter()は要素を「値」で取り出すのではなく、参照で取り出します。
② map(|x|x * 2)のxは&i32ですが、x * 2はi32型の値としか計算できません。
Rustでは、参照(&i32)と値(i32)は別物です。
なので、iter()の後に参照ではなく値にするためにcloned()やcopy()、変数の前に*を使用します。
毎回cloned()などをつけないといけないのは、ミスも起こりやすい…ので別の書き方があります!
iter_mut()
mut()とついているのでわかりやすいかと思いますが、これは可変参照イテレータを返します。
要素はそれぞれ&mut Tの形になります。
let mut numbers: Vec<i32> = vec![1, 2, 3, 4, 5];
for x in numbers.iter_mut() {
*x *= 2; //&mut i32
}
println!("{:?}",numbers); //[2, 4, 6, 8, 10]
参照なのであまり変わったように思えないかもしれませんが、iter()との違いは「値を変更できるか」ということです。
iter()はあくまで読み取りや表示をして別の変数にするのに対して、iter_mut()は変数名を変えずに値の変更を行うことができます。
into_iter()
所有権を移動するイテレーターを返します。
要素はそれぞれTとなり、参照ではなく値そのものになります。
let numbers: Vec<i32> = vec![1, 2, 3, 4, 5];
// into_iter() で所有権を取得して値を加工
let doubled: Vec<i32> = numbers.into_iter().map(|x| x * 2).collect();
println!("{:?}", doubled); // [2, 4, 6, 8, 10]
// println!("{:?}", numbers); // これはもう使えない
値そのものを使用するため、元のコレクションを使用できなくなります。
まとめ
| メソッド | イテレーターの型 | 所有権 | 値の変更 | 元のコレクションの使用 |
|---|---|---|---|---|
.iter() |
&T |
取らない | ×(読み取り専用) | 可能 |
.iter_mut() |
&mut T |
取らない | 〇(書き換え可能) | 可能 |
.into_iter() |
T |
移動する | 〇(値そのものを加工可能) | 不可(消費される) |
for文の書き方
iter()を使用したfor文の書き方例です!
fn main() {
//1. 配列やベクターの要素を順にループ
let numbers = [1, 2, 3, 4, 5];
for n in numbers {
println!("{}", n);
}
let vec_numbers = vec![10, 20, 30];
for n in &vec_numbers { // 参照を使う場合
println!("{}", n);
}
//2. インデックスと要素を同時に取得 (enumerate)
let numbers = vec![5, 10, 15];
for (i, n) in numbers.iter().enumerate() {
println!("index: {}, value: {}", i, n);
}
//3. 値を書き換えながらループ (iter_mut)
let mut numbers = vec![1, 2, 3];
for n in numbers.iter_mut() {
*n *= 2;
}
println!("{:?}", numbers); // [2, 4, 6]
//4. 所有権ごとループ (into_iter)
let numbers = vec![1, 2, 3];
for n in numbers.into_iter() {
println!("{}", n);
}
// numbers はここではもう使えない
//5. 範囲 (range) を使ったループ
for i in 0..5 { // 0 から 4 まで
println!("{}", i);
}
for i in 0..=5 { // 0 から 5 まで
println!("{}", i);
}
//6. ネストしたループ
let matrix = vec![
vec![1, 2],
vec![3, 4]
];
for row in &matrix {
for val in row {
println!("{}", val);
}
}
}
typescriptで書くとこんな感じ
//1. 配列やベクターの要素を順にループ
const numbersArr = [1, 2, 3, 4, 5];
for (const n of numbersArr) {
console.log(n);
}
const vecNumbers = [10, 20, 30];
for (const n of vecNumbers) { // TypeScriptでは参照の概念はない
console.log(n);
}
//2. インデックスと要素を同時に取得 (foreach)
const numbers = [5, 10, 15];
numbers.forEach((n, i) => {
console.log(`index: ${i}, value: ${n}`);
});
//3. 値を書き換えながらループ
let mutableNumbers = [1, 2, 3];
for (let i = 0; i < mutableNumbers.length; i++) {
mutableNumbers[i] *= 2;
}
console.log(mutableNumbers); // [2, 4, 6]
//4. 所有権ごとループ (typescriptには所有権の概念は必要ないので似せて作りました)
const numbers2 = [1, 2, 3];
const doubled = numbers2.map(x => x * 2);
for (const n of doubled) {
console.log(n);
}
//5. 範囲 (range) を使ったループ
// TypeScript には Rust の range 構文はないので、forループで代用
for (let i = 0; i < 5; i++) { // 0から4まで
console.log(i);
}
for (let i = 0; i <= 5; i++) { // 0から5まで
console.log(i);
}
//6. ネストしたループ
const matrix = [
[1, 2],
[3, 4]
];
for (const row of matrix) {
for (const val of row) {
console.log(val);
}
}
結構Typescriptと似ているので、for文については違和感はなさそう…
まとめ
Rustの配列やコレクションをループする仕組みであるiter()とfor文についてでした。
やっぱりここでも鬼門となるのは所有権…😇
iter()を使う際は、どの書き方が一番いいか見ながら書きましょうね!
Discussion