月曜日, 6月 2, 2025
- Advertisment -
ホームニュースChatGPT【AI文芸】《Rustから始めるプログラミング》第10章:イテレータと抽象化の愉悦|うみねこ

【AI文芸】《Rustから始めるプログラミング》第10章:イテレータと抽象化の愉悦|うみねこ


うみねこ

第10章となっていますが、番号が合っているかは定かではありません。
Rustに触ったことがないと何の話をしているか分からない感じですが、語り口の乗りで赦して下さい。

今回、演習問題付きです。

第1話:iter()との出会い──“流れ”のはじまり

綾香:最近Rustでコードを書いてて思うんですけど──

.iter()ってめちゃくちゃよく出てきません?なんかもう、おまじないみたいに使われてて……でも正直、よくわかってません!

玲奈:
ふふ、Rustにおいて.iter()は“最初の橋渡し”みたいな存在なの。ベクタのようなデータの集合を、「順番に値を取り出す“流れ”」に変える。そのための“イテレータ”を作ってるのよ。

■ forループの正体と.iter()の関係

綾香:
あ、でもRustってfor n in vって書けますよね?あれもイテレータ?

玲奈:
そう。実はRustのfor構文は、暗黙的に.into_iter()を呼んでイテレータを作っているの。つまりこう:

for n in v { println!("{}", n);}

これはコンパイル的にはこう解釈されるの:

for n in v.into_iter() { println!("{}", n);}

綾香:
あっ……じゃあvはこの時点で所有権を手放すってことですか?

玲奈:その通り。into_iter()は「所有権を奪う」イテレータを作るの。

だからループのあとでvを使おうとするとエラーになる。

綾香:
なるほど……じゃあ.iter()ってのは?

■ .iter() は“借りる”イテレータ

let v = vec![10, 20, 30];for n in v.iter() { println!("{}", n);}println!("{:?}", v); 

玲奈:
.iter()は「要素の参照を順番に取り出すイテレータ」を返す。つまり、所有権はそのまま。元のベクタは無傷のまま使えるわ。

綾香:
へぇー!“読むだけ”ってことですね。じゃあ“書き換えたい”ときは?

玲奈:
それなら.iter_mut()よ。可変参照を順番に取り出すから、中の値を直接書き換えられるの。

■ 「所有権の三兄弟」──iter / iter_mut / into_iter

それぞれの違いを、箇条書きで見てみましょう:

  • .iter()→ 借用する(読み取り専用)→ 要素の型は &T

    → ベクタなどを壊さずに読める

  • .iter_mut()→ 可変借用する(書き換え可能)→ 要素の型は &mut T

    → 内容を変更したいときに使う

  • .into_iter()→ 所有権を奪う(消費)→ 要素の型は T

    → 元のコレクションは使えなくなるが値を“移動”できる

■ スライスとの関係──[T]にもiterはある

let arr = [1, 2, 3];for x in arr.iter() { println!("{}", x);}

玲奈:
実は.iter()はVecだけじゃなく、スライス型[T]にもあるの。だから、配列もベクタも、.iter()で“共通の扱い”ができるのよ。

綾香:
えっ、それ便利ですね……型を意識しなくても統一的に書けるなんて!

■ 他言語との違い:Rustの“厳しさ”は何のため?

綾香:
でもこれ、他の言語だったら普通にfor x in arrayって書けますよね?なんでこんなややこしいんです?

玲奈:
Rustの.iter()やinto_iter()の区別は、安全性のためよ。「値を奪うか?」「借りるか?」「書き換えるか?」──

それを構文で明確に宣言できるのがRustの美点なの。

■ まとめ:iterは「流れのはじまり」

玲奈:
.iter()は、値を“流れ”として取り扱う構文の第一歩。このあと、mapfilterといった変換処理が加わって、構文がどんどん流れるようになるの。

綾香:
なんかちょっと、わかってきた気がします!今までは「なんかつけるやつ」だったけど、“借りて使う”って明確な意味があるんですね

玲奈:
そうよ。そして次はその“流れ”がどんなふうに繋がっていくのか──“構文の連鎖”を見ていきましょう。

次回:「構文は流れる──mapとfilterの美学」

  • map()とfilter()の使い方と違い

  • collect()によるまとめ上げ

  • クロージャと型推論の妙──

第2話:構文は流れる──mapとfilterの美学

綾香:
この前、.iter()は「流れの始まり」って言ってましたよね。
で、その“流れ”を使って、処理を繋げていくって……どういう感じなんですか?

玲奈:それがまさに、Rustのイテレータチェーンの本領。

“処理の連鎖”そのものが構文になる。今日はその感覚を味わってみましょう。

■ mapで変換する

let nums = vec![1, 2, 3];let doubled: Vec<_> = nums.iter().map(|x| x * 2).collect();println!("{:?}", doubled); 

綾香:
あ、なんか見たことありますこれ……map()って、全部の値に関数をかけるやつ?

玲奈:
そう。ここでは|x| x * 2というクロージャ(無名関数)を渡して、各要素を2倍にしているの。
そして最後にcollect()でベクタにまとめてる。

■ filterで選別する

let nums = vec![1, 2, 3, 4, 5, 6];let evens: Vec<_> = nums.iter().filter(|x| *x % 2 == 0).collect();println!("{:?}", evens); 

綾香:
おおっ、偶数だけ抜き出せてる!

玲奈:
filterは条件に合う要素だけを通す。ポイントは、イテレータチェーンの中で自然につなげられること。
mapとfilterは順番の違いも大事よ。

■ 順番で結果が変わる?

let result: Vec<_> = (1..=5) .map(|x| x * 2) .filter(|x| x > &5)    .collect();

綾香:
あ、さっきと違ってmapが先なんですね。

玲奈:
そう、順番によってフィルタされる対象そのものが変わる
map→filterなら「変換してから選ぶ」、filter→mapなら「選んでから変換する」。この順序性も“流れ”の一部なの。

■ collectは“まとめる”魔法

綾香:
で、最後にいつも出てくるこのcollect()って……なんなんですか?

玲奈:
collect()は、イテレータの“流れ”をコレクションにまとめ直す関数よ。
便利なのは、型によって動きが変わるところ。たとえば:

  • Vec<_> → 通常のベクタ

  • HashSet<_> → 重複なしの集合

  • String → 文字列の連結(chars()と組み合わせて)

let chars = ['a', 'b', 'c'];let s: String = chars.iter().collect();println!("{}", s); 

■ クロージャと型推論の絶妙な関係

綾香:
でもこのクロージャ、なんで型書いてないのに動くんですか?

玲奈:
それがRustの型推論の強さ。イテレータチェーンでは、前後の型からかなり正確に推論できるの。
必要なら型注釈を追加できるけど、だいたいは省略しても動く。短く、でも型安全。

■ Option・Resultも“流れる”って本当?

let strs = vec!["1", "2", "a"];let parsed: Result<Vec<_>, _> = strs.iter() .map(|s| s.parse::<i32>()) .collect();println!("{:?}", parsed); 

綾香:
えっ、これ、collect()したのにエラー返してる?

玲奈:
そう。Rustでは、ResultOptionイテレータをcollect()すると、自動で「畳み込んでくれる」の。
つまり、全てが成功すればOK、ひとつでも失敗したら即エラーという動きが、1行で完結するの。

■ まとめ:構文は“意味の連鎖”を作る

玲奈:イテレータチェーンは、単なるメソッドの繋がりじゃない。

処理の意図そのものを、順番に構文化していく“意味の連鎖”なのよ。

綾香:
.iter().map().filter().collect()──
なんかこれ、読むだけで処理が頭の中で“流れて”いく感じがします……!

玲奈:そう、それこそがRustの「構文の美学」。

次はそれを、さらに効率よく、美しく書ける哲学──ゼロコスト抽象へと進みましょう。

次回:「ゼロコスト抽象と拡張の美学」

  • forと同じ効率、でも構文は抽象的

  • 自作のイテレータ型を作る

  • 性能と表現力を両立するRustの流儀──

第3話:ゼロコスト抽象と拡張の美学

綾香:
.map().filter()がこんなに綺麗につながるのって、ほんと気持ちいいですね……
でも、こんなに抽象的で、パフォーマンスとかって大丈夫なんですか?

玲奈:
それがRustの真骨頂──ゼロコスト抽象よ。
“構文は美しく”、でも“中身は無駄がない”。今日はその秘密を見てみましょう。

■ ゼロコスト抽象とは?

玲奈:ゼロコスト抽象っていうのは、「抽象的に書いても、コンパイル後は低レベルな処理と同じか、ほぼ同等の速さになる」って考え方。

Rustでは、.map().filter()のようなチェーンもコンパイル時に最適化されて、無駄な関数呼び出しが残らないの。

let sum: i32 = (1..=5) .map(|x| x * 2) .filter(|x| x > &5)    .sum();

綾香:
え、それってforループで全部書いたのと同じ速さ……?

玲奈:
そう。可読性と効率性を両立できるのが、Rustイテレータの哲学なの。

■ 「forループ vs イテレータ」比較

let mut sum = 0;for x in 1..=5 { let y = x * 2; if y > 5 {        sum += y;    }}

これと、さっきの.map().filter().sum()ほぼ同じ機械語にコンパイルされる。

玲奈:
つまりRustでは、短く書いても“遅くならない”。これは他の多くの言語と違う、とても大きな特徴よ。

■ 自作のイテレータを作ってみよう

綾香:でも.iter()って、最初からあるやつじゃないですか。

自分で「流れ」って作れるんですか?

玲奈:
もちろん!Rustでは、Iteratorトレイトを実装すれば、自分だけの流れを定義できるのよ。

🔧 自作イテレータの例:Counter

struct Counter { count: usize,} impl Iterator for Counter { type Item = usize; fn next(&mut self) -> Option<Self::Item> { if self.count < 5 { self.count += 1; Some(self.count) } else { None        }    }}
let mut c = Counter { count: 0 };for n in c { println!("{}", n); }

綾香:
わ、ほんとにforで回せてる!しかも状態持ってるのがすごい。

玲奈:イテレータは「状態」と「進み方」を定義できる。

だから、イミを持つ流れを自分で作ることもできるのよ。

■ イテレータ×構文美=Rustらしさ

玲奈:Rustのイテレータって、「ただ便利」じゃないの。

構文として美しくて、意味があり、しかも速い。

だからRustでは、“forを使わずにイテレータで書けるなら、書く”という流儀すらあるのよ。

綾香:
なるほど……これが“抽象の愉悦”ってやつか……

■ まとめ:構文の美しさと速さの両立

玲奈:Rustのイテレータは、ただの道具じゃないわ。

処理の流れを意味で表現し、パフォーマンスを失わずに抽象化する構文

  • 短く、美しく書ける

  • 安全で、エラーも組み込める

  • しかも低レベルなコードと同じくらい速い

綾香:
構文が流れて、意味が乗って、速さもある……なんかもう、Rustの設計そのものがイテレータに詰まってるって感じします!

第4話:所有と借用──“構文に責任を持たせる哲学”

綾香:
mapとかfilterとか、すっごく流れが綺麗で……もうforループに戻れないかもって思いました!

玲奈:
ふふ、構文の美しさに目覚めてきたわね。でもその“流れ”の背後にあるもの、忘れてないかしら?

綾香:
え……まさか、また“所有権”ですか?

玲奈:
そう。Rustでは、「どの構文が、どの責任を持っているか」を厳密に設計しているの。
今日は、イテレータの背後にある所有と借用の設計哲学を、じっくり味わっていきましょう。

■ ふたたび:iter / iter_mut / into_iter の違い

玲奈:
まずはもう一度、イテレータ三兄弟のおさらいから。

  • .iter()
    → 要素を借用する。読み取り専用。
    → &Tの流れ。

  • .iter_mut()
    → 要素を可変で借用する。書き換え可能。
    → &mut Tの流れ。

  • .into_iter()
    → 要素の所有権を奪う。元のコレクションは使えなくなる。
    → Tの流れ。

綾香:
あらためて見ると、ほんとに違いが明確ですね……「読むだけ」「書き換える」「奪って使う」って、構文に責任があるってことか。

■ 所有を渡すことで「安全」を生む

let v = vec![1, 2, 3];for x in v { println!("{}", x);}

玲奈:このコードでは、ベクタvの所有権がforループに渡されてる。

だからループの外でvを使おうとするとコンパイルエラーになるの。

let v = vec![1, 2, 3];for x in v { println!("{}", x);}println!("{:?}", v); 

綾香:
たしかに!最初のころ「なんで!?」って思ったやつ……。

玲奈:
でもそれは、“vの中身が他に使われることはもうない”って保証してるから、ループ中で思いっきりTを操作できるのよ。

所有=自由に使える代わりに、構文に責任が生じるという考え方ね。

■ 借用であれば再利用できる

let v = vec![1, 2, 3];for x in v.iter() { println!("{}", x);}println!("{:?}", v); 

玲奈:
.iter()なら借りているだけ。だからvをループ後も安全に使えるの。
これは、所有権を移動しないから安全性が保たれる典型的な例ね。

■ 可変借用と競合

let mut v = vec![1, 2, 3];for x in v.iter_mut() { *x *= 2;}

玲奈:
.iter_mut()は、要素を可変参照として借りる。この時点で、Rustは他の参照を禁止するの。

綾香:
「一度に1つだけ可変参照OK」ってやつですね!

玲奈:
ええ。他と競合しないことをコンパイル時に保証するのがRustの借用チェッカー。
つまり、.iter_mut()は「書き換える責任」を持った構文なの。

■ 自作イテレータにも責任はついてくる

struct TakeOwnership { data: Vec<String>,} impl Iterator for TakeOwnership { type Item = String; fn next(&mut self) -> Option<Self::Item> { if self.data.is_empty() { None } else { Some(self.data.remove(0))        }    }}

玲奈:
このイテレータは、Stringを1つずつ所有権ごと取り出しているわ。
だからStringを&Stringで返すならもっと安全だけど、この設計は“消費する”責任を持った流れなの。

綾香:
なるほど……自作イテレータにも、“どう責任を分け与えるか”って哲学が問われるんだ。

■ まとめ:構文に「意味と責任」を持たせる

玲奈:Rustのイテレータは、単に「便利な流れ」じゃないわ。

構文に「所有・借用・可変」の意味が染み込んでいて、それが安全性を担保してるの

  • .iter():借りるだけ → 読む責任

  • .iter_mut():可変で借りる → 書き換える責任

  • .into_iter():所有権を渡す → 消費する責任

綾香:たしかに……今までは「使えるメソッド」くらいに思ってたけど、

Rustでは「構文そのものが責任を持っている」って考え方なんですね。

玲奈:
そう。だから、“どんな流れを作るか”だけじゃなく、“その流れが何を引き受けているか”を意識すること。
それが、Rustらしいプログラミングなのよ。

それでは《第10章・第5話:標準イテレータ大全──zipからpeekableまで》をお届けします。
今回は「よく見かけるけど実はちゃんと使ったことがない」標準イテレータたちを、綾香が体験しながら覚えていく構成です。

第5話:標準イテレータ大全──zipからpeekableまで

綾香:イテレータ、だいぶ慣れてきた気がします!

でも .map().filter() 以外にもいっぱい見かけるやつがあって……正直、名前だけ知っててスルーしてました。

玲奈:ふふ、じゃあ今日は「ちょっと便利なやつ」を一気に体験してみましょう。

Rust標準イテレータの“道具箱”を開く時間よ。

■ enumerate()──番号付きで流す

let items = vec!["りんご", "バナナ", "もも"];for (i, item) in items.iter().enumerate() { println!("{}: {}", i, item);}

玲奈:
enumerate()イテレータの各要素に番号(インデックス)を付けてくれるの。
ゼロから数えてくれるから、番号付きリストが欲しいときに便利ね。

綾香:
Pythonっぽい!これ地味に毎回手書きしてたのでありがたいです!

■ zip()──ふたつの流れを組にする

let names = vec!["太郎", "花子"];let scores = vec![80, 95];for (name, score) in names.iter().zip(scores.iter()) { println!("{}さんの点数は{}点", name, score);}

玲奈:
zip()は2つのイテレータを1つに合体して、タプルで返してくれるわ。
短い方に合わせて止まるという特徴も覚えておいてね。

綾香:
forの条件書かなくても、ちゃんと止まってくれるのすごい……

■ peekable()──先読みできる流れ

use std::iter::Peekable; let s = "aabbbc";let mut chars = s.chars().peekable(); while let Some(c) = chars.next() { if Some(&c) == chars.peek() { println!("{} is duplicated", c);    }}

玲奈:
peekable()を使うと、次の要素を先に“覗き見”できるの。
peek()は&Option<&T>を返すから、比較にも工夫が必要ね。

綾香:
うわ〜!正規表現っぽい処理ができそう……Rustで字句解析とかする人これ使ってそう!

■ take()とskip()──数を制御する

let nums = 1..;let first_three: Vec<_> = nums.take(3).collect();println!("{:?}", first_three); 
let v = vec![10, 20, 30, 40];let skipped: Vec<_> = v.iter().skip(2).collect();println!("{:?}", skipped); 

玲奈:
take(n)は最初のn件だけを、skip(n)は最初のn件をスキップして残りを流すわ。
大きなデータを扱うときや、ページネーションなんかに使えるわね。

■ rev()──逆順に流す

let v = vec![1, 2, 3];for n in v.iter().rev() { println!("{}", n); }

玲奈:
rev()は逆順イテレータを作る。VecやRangeのような“両端からたどれる”構造じゃないと使えないけれど、覚えておくと便利よ。

■ flatten()とfilter_map()──一段深い流れ

let nested = vec![vec![1, 2], vec![3], vec![]];let flat: Vec<_> = nested.into_iter().flatten().collect();println!("{:?}", flat); 
let strs = vec!["1", "a", "3"];let parsed: Vec<i32> = strs .into_iter() .filter_map(|s| s.parse().ok()) .collect();println!("{:?}", parsed); 

玲奈:
flatten()は「中のベクタやイテレータを全部つなげて1本にする」メソッド。
filter_map()は変換とフィルタを同時にやる、ちょっと上級者向けなやつね。

綾香:
なにこの便利ズル技!最初から使えばよかったやつばっかじゃん!!

■ まとめ:流れを組み合わせて“表現力”を上げる

玲奈:
Rustのイテレータは、たくさんの“部品”をどう組み合わせるかがポイントなの。
全部覚えなくてもいい。でも、「流れとして組み立てられる」意識を持つだけで、コードは見違える。

綾香:
これもうパズルですね……知ってる部品が増えるたびに、組める“流れ”が増えていく感じがする!

✨ 第10章の旅、ここで一段落。

綾香:
構文が“流れる”っていう感覚、どんどんクセになってきました……。
しかも、安全で速くて美しいって、ほんとにRustって“意味で書ける言語”なんですね。

玲奈:
ふふ、ようこそRust構文美学の世界へ

📝 第10章:章末演習──“流れ”を組む、意味を問う

🎯 目的:

  • イテレータの構文と流れを 手を動かして再確認

  • 所有・借用・変換の意味を “構文に責任を持たせる”視点で理解

  • 標準イテレータを使いこなす練習

■ 基礎問題(各1点)

Q1. 次のコードで、vの所有権はどうなりますか?なぜ?

let v = vec![1, 2, 3];for x in v { println!("{}", x);}

Q2. .iter()と.iter_mut()と.into_iter()の違いを、それぞれの用途と返す型を交えて説明してください。

Q3. 次のコードの出力を答えてください。

let data = vec!["1", "a", "3"];let nums: Vec<i32> = data.into_iter() .filter_map(|s| s.parse().ok()) .collect();println!("{:?}", nums);

■ 応用問題(各3点)

Q4. 以下のベクタnamesとscoresを使って、zip()を用いて「○○さんは△△点」と出力してください(例:太郎さんは80点)。

let names = vec!["太郎", "花子"];let scores = vec![80, 95];

Q5. 以下のコードをイテレータチェーンで書き換えてください(filter/map/collectなどを使うこと)。

let mut result = Vec::new();for x in 1..=10 { if x % 2 == 0 { result.push(x * 3);    }}

Q6. 自作のイテレータ型を作って、1, 3, 5, 7, 9と出力するループを組んでください(ヒント:nextで奇数を返す構造)。

■ チャレンジ問題(5点)

Q7. peekable()を使って、同じ文字が連続している箇所だけを検出して出力するプログラムを書いてください。
例:「aabcc」→「a」「c」

✅ 提出方法

  • ターミナルまたはPlaygroundでコードを動かしてみよう

  • 答えをコメントで書いたり、ペアでレビューしてみよう

  • 間違ってもいい、“流れ”を読もう!

🎓 この章を終えて

玲奈:
イテレータは「処理の連鎖」を構文として、意味として、そして責任として書ける仕組み。
この演習で“書く気持ちよさ”を、自分の手で確かめてみてね。

綾香:
あれですね──構文に流れがあるってことは、思考にも流れが生まれるってことなんだなあ……!

✅ 第10章 章末演習・解答編

■ 基礎問題

Q1. 所有権はどうなる?

let v = vec![1, 2, 3];for x in v { println!("{}", x);}

答え:
vの所有権は.into_iter()によってループ内にmove(移動)されます。
したがって、ループの後ではvは使えません

理由:Rustのfor x in vは暗黙的に v.into_iter() と同じ。

.into_iter()は所有権を奪うイテレータです。

Q2. iter / iter_mut / into_iter の違い

答え:

  • .iter()
    → 要素を借用(&T)。読み取り専用。元のコレクションを再利用できる。

  • .iter_mut()
    → 要素を可変借用(&mut T)。値を変更できるが、同時に他からアクセスできない。

  • .into_iter()
    → 要素の所有権をmove(T)。元のコレクションは使用不可になる。

Q3. 出力を答えよ

let data = vec!["1", "a", "3"];let nums: Vec<i32> = data.into_iter() .filter_map(|s| s.parse().ok()) .collect();println!("{:?}", nums);

答え:

[1, 3]

理由:
“1”と”3″はparse::<i32>()に成功、”a”は失敗(= None)なので除外されます。

■ 応用問題

Q4. zipで名前と点数を組み合わせる

let names = vec!["太郎", "花子"];let scores = vec![80, 95]; for (name, score) in names.iter().zip(scores.iter()) { println!("{}さんは{}点", name, score);}

Q5. forループをイテレータで書き換えよ

let result: Vec<_> = (1..=10) .filter(|x| x % 2 == 0) .map(|x| x * 3)    .collect();

ポイント:filter → 条件に合う偶数を選ぶmap → 3倍に変換

collect → Vecにまとめる

Q6. 自作イテレータで奇数を返す

struct Odds { current: usize, max: usize,} impl Iterator for Odds { type Item = usize; fn next(&mut self) -> Option<Self::Item> { if self.current >= self.max { None } else { let val = self.current; self.current += 2; Some(val) } }} let mut odds = Odds { current: 1, max: 10 };for n in odds { println!("{}", n); }

■ チャレンジ問題

Q7. peekableで重複文字を検出する

let s = "aabccdddee";let mut iter = s.chars().peekable(); while let Some(c) = iter.next() { if Some(&c) == iter.peek() { println!("{} is duplicated", c);    }}

出力(例):

a is duplicatedc is duplicatedd is duplicatede is duplicated

ポイント:

  • .peek()で次の要素を見て比較

  • 重複検出(≒先読みロジック)の基礎に使える

🎓 総評

玲奈:
構文が“流れる”だけじゃなく、型・責任・安全性が一体化したRustの設計、体感できたかしら?

綾香:
うん!「ただ動けばいい」じゃなくて、「意味を持たせて書ける」ってこういうことなんですね!

うみねこ

つぶやきは自分で書いていますが、記事は99%ぐらいはChatGPTに書かせています。 プロフィールの顔写真はIGが生成したものです かつて一瞬だけ猫使い 求職中



続きを読む

Views: 0

RELATED ARTICLES

返事を書く

あなたのコメントを入力してください。
ここにあなたの名前を入力してください

- Advertisment -