Rustプログラムでは、高性能かつ安全なコードを簡潔に記述できる特性を最大限に活かすために、イテレーションの仕組みが重要な役割を果たします。RustのIterator
トレイトを実装することで、標準的なループ以上に柔軟でカスタマイズ可能な反復処理を作ることが可能です。本記事では、Iterator
トレイトの基本から、独自のロジックを実現するためのカスタムIteratorの作成、実践的な活用例に至るまで、詳細に解説します。これにより、より洗練されたRustプログラムを書くためのスキルを習得できるでしょう。
Iteratorトレイトの基本概念
RustのIterator
トレイトは、コレクションやデータの一連の要素を順番に処理するための標準的なインターフェースを提供します。このトレイトを実装することで、独自のデータ構造を反復処理できるようになります。
Iteratorトレイトとは
Iterator
トレイトは、データの反復処理を抽象化するRust標準ライブラリの一部です。主にnext
メソッドを実装することで、イテレーションの仕組みを定義します。
Iteratorの主要メソッド
next
メソッド: Iteratorトレイトの中核となるメソッドで、次の要素を返します。要素が無い場合はNone
を返します。- チェーン可能なメソッド:
map
やfilter
など、Iteratorを活用して複雑な処理を組み合わせるメソッドが提供されています。
標準ライブラリにおける例
例えば、Rust標準のVec
型にはデフォルトでiter
メソッドが用意されており、これはIterator
を返します。以下のコードはその基本的な使用例です:
let numbers = vec![1, 2, 3];
let mut iter = numbers.iter();
while let Some(num) = iter.next() {
println!("{}", num);
}
このコードは、Vec
の要素を順番に取り出して処理するイテレーションの典型例です。
基本概念の重要性
Iterator
を理解することで、標準の反復処理を超えた柔軟なロジックを構築できます。また、Iterator
はメモリ効率や安全性を最大化するために設計されており、大規模データや複雑な処理に適しています。
Iteratorを実装するメリット
柔軟なループ処理
Iterator
トレイトを実装することで、標準的なループ構造(for
やwhile
)を超えた柔軟なデータ処理が可能になります。
例えば、特定の条件を満たす要素だけを抽出するようなカスタムロジックを容易に組み込むことができます。
コードの簡潔化
カスタムIteratorを作成することで、同様の処理を繰り返すコードを抽象化し、簡潔で読みやすいコードを書くことができます。これにより、コードの保守性が向上し、バグのリスクが低下します。
具体例
以下は、Iterator
を使って特定の条件を満たす要素を処理する例です。
struct EvenNumbers {
current: usize,
max: usize,
}
impl Iterator for EvenNumbers {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
self.current += 1;
if self.current <= self.max && self.current % 2 == 0 {
Some(self.current)
} else {
None
}
}
}
let even_numbers = EvenNumbers { current: 0, max: 10 };
for num in even_numbers {
println!("{}", num);
}
このコードでは、偶数のみを生成するカスタムIteratorを実装しています。
パフォーマンスと安全性の向上
RustのIterator
は怠惰評価(lazy evaluation)を採用しており、必要な要素だけを順次生成します。これにより、不要なメモリ使用を避け、高いパフォーマンスを実現します。また、Rustの所有権システムにより、メモリ安全性が保証されます。
再利用可能なロジックの設計
カスタムIteratorは、再利用可能なロジックを設計する上でも重要です。異なるプロジェクトやモジュール間で共通する処理を抽象化して使い回すことができます。
実践での価値
カスタムIteratorを実装することにより、データ処理のパイプラインを簡単に構築でき、複雑な処理も直感的に表現できます。特に、データを扱うアプリケーションや高性能が求められるシステムで効果を発揮します。
Iteratorトレイトの構造と必要なメソッド
Iteratorトレイトの概要
RustのIterator
トレイトは、データの反復処理を抽象化するための基本的な構造を提供します。このトレイトを実装することで、独自のデータ構造を順番に処理できるようになります。特に重要なのは、next
メソッドの実装です。
Iteratorトレイトの定義
以下は、標準ライブラリで定義されているIterator
トレイトの基本的な形です:
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
type Item
: Iteratorが生成する要素の型を指定します。next
メソッド: Iteratorの次の要素を返すメソッドです。要素がない場合はNone
を返します。
必須メソッド: `next`の実装
next
メソッドの実装は、Iteratorトレイトを機能させるために必須です。このメソッドが呼び出されるたびに次の要素が生成されます。以下に、シンプルな例を示します:
struct Counter {
count: usize,
max: usize,
}
impl Iterator for Counter {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
if self.count < self.max {
self.count += 1;
Some(self.count)
} else {
None
}
}
}
この例では、Counter
という構造体が最大値までの数値を順に返すIteratorとして実装されています。
オプションメソッドとチェーン可能性
Iterator
トレイトを実装すると、自動的に多くのメソッドが利用可能になります。これには、以下のようなチェーン可能なメソッドが含まれます:
map
: 各要素を変換します。filter
: 条件を満たす要素のみを通します。collect
: 要素をコレクションに変換します。
以下は、Iteratorメソッドを組み合わせた例です:
let result: Vec<usize> = Counter { count: 0, max: 10 }
.map(|x| x * 2)
.filter(|x| x % 3 == 0)
.collect();
println!("{:?}", result);
このコードは、Counter
の値を2倍し、そのうち3で割り切れる値をVec
として収集します。
まとめ
Iterator
トレイトの基本構造とnext
メソッドの実装は、カスタムIteratorを作る際の出発点です。この仕組みにより、柔軟で再利用可能なデータ処理を効率的に行うことができます。また、標準のIteratorメソッドを活用することで、さらに強力なデータ操作が可能になります。
基本的なカスタムIteratorの作成例
簡単なIteratorの構築
カスタムIteratorを作成する基本的な手順を学ぶために、単純なカウンターを実装する例を紹介します。このカウンターは1から指定された最大値までの整数を順番に返します。
カスタムIteratorの例: カウンター
以下のコードは、Counter
という構造体を使ったカスタムIteratorの実装例です:
struct Counter {
current: usize,
max: usize,
}
impl Counter {
fn new(max: usize) -> Self {
Counter { current: 0, max }
}
}
impl Iterator for Counter {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
self.current += 1;
if self.current <= self.max {
Some(self.current)
} else {
None
}
}
}
fn main() {
let counter = Counter::new(5);
for num in counter {
println!("{}", num);
}
}
コードの説明
- 構造体
Counter
:current
で現在の値、max
でカウントの上限値を保持します。 Counter::new
メソッド: 初期化用のコンストラクタ。next
メソッド:current
を1ずつ増加させ、上限値に達するまでSome
を返します。それ以降はNone
を返してイテレーションを終了します。for
ループ: 標準のfor
ループでIteratorを使い、各要素を順番に処理します。
カスタムIteratorの活用
カスタムIteratorを作成することで、標準のイテレーションロジックでは実現しにくい独自の処理を簡潔に表現できます。この例では、範囲内の整数を生成する単純な機能を実装しましたが、さらに高度なロジックを組み込むことも可能です。
応用例: 偶数カウンター
以下のコードでは、カスタムIteratorを改良し、偶数のみを生成するように変更します:
struct EvenCounter {
current: usize,
max: usize,
}
impl EvenCounter {
fn new(max: usize) -> Self {
EvenCounter { current: 0, max }
}
}
impl Iterator for EvenCounter {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
self.current += 2;
if self.current <= self.max {
Some(self.current)
} else {
None
}
}
}
fn main() {
let even_counter = EvenCounter::new(10);
for num in even_counter {
println!("{}", num);
}
}
偶数カウンターの特徴
- ステップを2に設定することで偶数のみを生成。
current
がmax
を超えた場合にNone
を返して終了。
まとめ
基本的なカスタムIteratorの実装により、Rustプログラムで柔軟なデータ処理を可能にします。この知識を基に、特定のニーズに応じた高度なIteratorを構築できます。Rustの所有権システムと組み合わせて、安全かつ効率的なプログラムを実現しましょう。
ライフタイムと所有権の考慮
Iteratorにおけるライフタイム
Rustでは、メモリの安全性を保証するために所有権とライフタイムのルールが適用されます。カスタムIteratorを設計する際、ライフタイムを正しく設定することが重要です。特に、Iteratorが参照を返す場合はライフタイムの明示が必要です。
ライフタイムの基本
ライフタイムとは、参照が有効である期間をRustがコンパイル時に追跡するための仕組みです。Iteratorが返す要素が参照である場合、ライフタイムを明示する必要があります。
以下は、ライフタイムを使用したカスタムIteratorの例です:
struct SliceIterator<'a, T> {
slice: &'a [T],
index: usize,
}
impl<'a, T> SliceIterator<'a, T> {
fn new(slice: &'a [T]) -> Self {
SliceIterator { slice, index: 0 }
}
}
impl<'a, T> Iterator for SliceIterator<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
if self.index < self.slice.len() {
let result = &self.slice[self.index];
self.index += 1;
Some(result)
} else {
None
}
}
}
fn main() {
let numbers = vec![1, 2, 3, 4];
let slice_iter = SliceIterator::new(&numbers);
for num in slice_iter {
println!("{}", num);
}
}
コードのポイント
SliceIterator
構造体: スライス(&[T]
)を元に要素を反復するカスタムIteratorです。- ライフタイム注釈:
<'a>
を使って、返される参照がスライスのライフタイムに従うことを明示します。 next
メソッド: スライスの要素を順番に返します。
所有権の考慮
カスタムIteratorを設計する際、所有権の扱いも重要です。所有権を持つIteratorは、データを完全に移動する場合に役立ちます。
所有権を持つIteratorの例
以下の例では、所有権を持つIteratorを作成します:
struct OwnedIterator<T> {
items: Vec<T>,
}
impl<T> OwnedIterator<T> {
fn new(items: Vec<T>) -> Self {
OwnedIterator { items }
}
}
impl<T> Iterator for OwnedIterator<T> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
if !self.items.is_empty() {
Some(self.items.remove(0))
} else {
None
}
}
}
fn main() {
let items = vec![10, 20, 30];
let mut owned_iter = OwnedIterator::new(items);
while let Some(item) = owned_iter.next() {
println!("{}", item);
}
}
コードのポイント
Vec<T>
を所有: 内部のVec<T>
が完全にOwnedIterator
に所有されます。- 要素の移動:
remove
を使用して要素を取り出し、Iteratorが返すたびにVec<T>
の内容が変化します。
ライフタイムと所有権のバランス
- 参照ベースのIterator: データを共有しつつ、メモリを効率的に使いたい場合に適しています。
- 所有権を持つIterator: データを消費し、独立して処理したい場合に便利です。
まとめ
Rustの所有権とライフタイムの仕組みを理解し、適切に設計することで、安全かつ効率的なカスタムIteratorを作成できます。所有権を持つIteratorと参照を返すIteratorの両方を使い分けることで、多様なユースケースに対応できます。
高度なIteratorの活用例
複雑なロジックを含むカスタムIterator
基本的なIteratorに加え、より高度なロジックを組み込んだカスタムIteratorを作成することで、データ処理を効率化できます。以下では、条件に基づいて要素を生成するIteratorを実装します。
高度な例: フィボナッチ数列の生成
フィボナッチ数列を生成するIteratorを作成する例を示します:
struct Fibonacci {
current: u64,
next: u64,
max: u64,
}
impl Fibonacci {
fn new(max: u64) -> Self {
Fibonacci {
current: 0,
next: 1,
max,
}
}
}
impl Iterator for Fibonacci {
type Item = u64;
fn next(&mut self) -> Option<Self::Item> {
if self.current > self.max {
None
} else {
let new_next = self.current + self.next;
let result = self.current;
self.current = self.next;
self.next = new_next;
Some(result)
}
}
}
fn main() {
let fib = Fibonacci::new(100);
for num in fib {
println!("{}", num);
}
}
コードのポイント
Fibonacci
構造体: 現在の値と次の値を保持します。- 上限値の設定: イテレーションを終了する条件を
max
で制御します。 - 計算ロジック:
next
メソッド内でフィボナッチ数を順次計算して返します。
複数の条件を組み合わせる例
条件に基づいてデータをフィルタリングしつつ、加工して返すIteratorの例を示します:
struct EvenSquare {
current: usize,
max: usize,
}
impl EvenSquare {
fn new(max: usize) -> Self {
EvenSquare { current: 0, max }
}
}
impl Iterator for EvenSquare {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
while self.current <= self.max {
self.current += 1;
if self.current % 2 == 0 {
return Some(self.current * self.current);
}
}
None
}
}
fn main() {
let even_square = EvenSquare::new(10);
for num in even_square {
println!("{}", num);
}
}
コードのポイント
- 偶数の平方計算: 偶数のみを選択し、その平方を計算して返します。
- 条件付きの反復処理:
while
ループで条件をチェックしつつ要素を生成します。
Iteratorの状態を追跡する例
状態を持つIteratorを設計することで、複数の条件や動的なロジックを処理することが可能です。以下の例では、要素の合計値を追跡しながら生成します:
struct AccumulatingIterator {
current: usize,
max: usize,
total: usize,
}
impl AccumulatingIterator {
fn new(max: usize) -> Self {
AccumulatingIterator {
current: 0,
max,
total: 0,
}
}
}
impl Iterator for AccumulatingIterator {
type Item = (usize, usize);
fn next(&mut self) -> Option<Self::Item> {
if self.current < self.max {
self.current += 1;
self.total += self.current;
Some((self.current, self.total))
} else {
None
}
}
}
fn main() {
let acc_iter = AccumulatingIterator::new(5);
for (value, total) in acc_iter {
println!("Value: {}, Total: {}", value, total);
}
}
コードのポイント
- 状態の追跡:
total
を利用して累積値を保持。 - ペアの返却: 現在の値と累積値をペアで返します。
まとめ
高度なカスタムIteratorを活用することで、複雑なデータ処理や条件に基づく反復処理を効率的に行うことができます。Rustの安全で高性能な特徴を活かしつつ、柔軟で再利用可能なロジックを構築できます。これらの応用例をベースに、さらに複雑なIteratorを設計することが可能です。
Iteratorチェーンの構築
Iteratorチェーンとは
Iteratorチェーンとは、複数のIteratorメソッドを連続的に呼び出し、データを変換またはフィルタリングしながら処理する方法です。このアプローチにより、効率的で読みやすいデータ処理パイプラインを構築できます。
Iteratorチェーンの基本例
以下のコードは、数値の範囲から偶数をフィルタリングし、それらを2倍に変換して合計する例です:
fn main() {
let sum: i32 = (1..10) // 1から9までの範囲
.filter(|x| x % 2 == 0) // 偶数のみを選択
.map(|x| x * 2) // 各値を2倍に変換
.sum(); // 合計を計算
println!("Sum of doubled even numbers: {}", sum);
}
コードのポイント
filter
: 偶数のみを選択する条件を指定。map
: 各要素を2倍に変換。sum
: 結果を集約して合計を返します。
この例では、Iteratorメソッドが連鎖して呼び出され、簡潔で直感的なコードを実現しています。
カスタムIteratorとチェーンの組み合わせ
カスタムIteratorを標準のIteratorチェーンに組み込むことで、さらに柔軟な処理が可能です。
struct Counter {
current: usize,
max: usize,
}
impl Counter {
fn new(max: usize) -> Self {
Counter { current: 0, max }
}
}
impl Iterator for Counter {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
self.current += 1;
if self.current <= self.max {
Some(self.current)
} else {
None
}
}
}
fn main() {
let result: Vec<usize> = Counter::new(10)
.filter(|&x| x % 2 == 0) // 偶数を選択
.map(|x| x * x) // 各要素の平方を計算
.collect(); // ベクタとして収集
println!("{:?}", result);
}
コードのポイント
- カスタムIterator
Counter
が生成する範囲内の整数を使用。 filter
とmap
を組み合わせてデータを変換。collect
メソッドで結果をベクタに収集。
Iteratorチェーンの応用: データ処理パイプライン
以下は、CSVデータを処理するような現実的なユースケースを示します:
fn main() {
let data = vec!["1,John", "2,Jane", "3,Bob"];
let processed: Vec<(i32, String)> = data
.iter()
.map(|line| {
let mut parts = line.split(',');
let id = parts.next().unwrap().parse::<i32>().unwrap();
let name = parts.next().unwrap().to_string();
(id, name)
}) // 各行をパースしてタプルに変換
.filter(|(id, _)| *id % 2 == 1) // IDが奇数のものだけを選択
.collect(); // 結果をベクタに収集
println!("{:?}", processed);
}
コードのポイント
split
: 文字列をカンマで分割。parse
: IDを整数に変換。filter
: IDが奇数の行のみを選択。
Iteratorチェーンの利点
- コードの簡潔化: 複数の操作を一連のメソッド呼び出しで表現できるため、コードが短く、読みやすくなります。
- 効率性: RustのIteratorは怠惰評価(lazy evaluation)を採用しており、必要な要素だけが生成されるため、メモリ効率が高い。
- 柔軟性: カスタムIteratorと標準のチェーンメソッドを組み合わせることで、多様なユースケースに対応可能。
まとめ
Iteratorチェーンは、Rustの強力な機能の一つであり、複雑なデータ処理を簡潔に記述できます。標準IteratorとカスタムIteratorを組み合わせることで、柔軟で効率的なパイプラインを構築し、さまざまなユースケースに対応できます。
より実用的な応用例
カスタムIteratorを活用した応用例
実用的なカスタムIteratorのユースケースを紹介します。これにより、実際のプロジェクトでの使用方法をイメージしやすくなります。
応用例1: ページネーション処理
Webアプリケーションやデータベースクエリで、データをページごとに取得する機能を実現するカスタムIteratorを作成します。
struct Paginator {
current_page: usize,
total_pages: usize,
page_size: usize,
data: Vec<usize>,
}
impl Paginator {
fn new(data: Vec<usize>, page_size: usize) -> Self {
let total_pages = (data.len() + page_size - 1) / page_size;
Paginator {
current_page: 0,
total_pages,
page_size,
data,
}
}
}
impl Iterator for Paginator {
type Item = Vec<usize>;
fn next(&mut self) -> Option<Self::Item> {
if self.current_page < self.total_pages {
let start = self.current_page * self.page_size;
let end = usize::min(start + self.page_size, self.data.len());
self.current_page += 1;
Some(self.data[start..end].to_vec())
} else {
None
}
}
}
fn main() {
let data = (1..25).collect::<Vec<_>>();
let paginator = Paginator::new(data, 5);
for page in paginator {
println!("{:?}", page);
}
}
コードのポイント
- データ分割:
Vec
をページごとに分割。 - ページネーションロジック: 現在のページを追跡し、次のページを返す。
- 汎用性: ページサイズを動的に設定可能。
応用例2: リアルタイムデータフィルタリング
センサーやストリームデータから特定の条件を満たすデータだけを処理するIteratorを構築します。
struct SensorData {
data: Vec<f64>,
threshold: f64,
}
impl SensorData {
fn new(data: Vec<f64>, threshold: f64) -> Self {
SensorData { data, threshold }
}
}
impl Iterator for SensorData {
type Item = f64;
fn next(&mut self) -> Option<Self::Item> {
while let Some(&value) = self.data.first() {
self.data.remove(0);
if value > self.threshold {
return Some(value);
}
}
None
}
}
fn main() {
let sensor_data = vec![10.5, 20.3, 15.0, 8.7, 25.4];
let mut filtered_data = SensorData::new(sensor_data, 15.0);
while let Some(data) = filtered_data.next() {
println!("Filtered Data: {}", data);
}
}
コードのポイント
- 条件付きフィルタリング: データが閾値を超える場合のみ返す。
- リアルタイムデータ処理: データストリームから順次処理。
応用例3: データパイプラインの構築
複数のIteratorを連鎖させたデータパイプラインを構築します。
fn main() {
let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let result: Vec<_> = data
.into_iter()
.filter(|&x| x % 2 == 0) // 偶数を選択
.map(|x| x * 3) // 3倍に変換
.collect(); // ベクタに収集
println!("{:?}", result);
}
コードのポイント
- 組み合わせ:
filter
とmap
を使用して複数の処理を連鎖。 - 効率性: Iteratorチェーンの怠惰評価を活用。
応用例の利点
- データの段階的処理: ページネーションやリアルタイムフィルタリングなど、実用的な処理を簡潔に表現可能。
- 拡張性: 新しい条件や操作を簡単に追加。
- パフォーマンス: メモリ効率を考慮した設計。
まとめ
カスタムIteratorを活用することで、複雑なデータ処理を安全かつ効率的に行えます。応用例を基に、自身のプロジェクトに適したカスタムIteratorを設計し、より強力なRustプログラムを構築しましょう。
演習問題
学習した内容を定着させるために、以下の演習問題に取り組んでみてください。これらは実践的なカスタムIteratorの設計スキルを養うのに役立ちます。
問題1: 奇数カウンターの作成
1から指定された範囲の最大値までの奇数のみを返すカスタムIteratorを実装してください。
要件:
- 奇数のみを生成するロジックを実装。
next
メソッドを適切に設計。
例:
let odd_counter = OddCounter::new(10);
for num in odd_counter {
println!("{}", num);
}
// 出力: 1, 3, 5, 7, 9
問題2: フィルタリングと変換を組み合わせたIterator
指定された範囲の整数のうち、5の倍数だけを選択し、それを2倍に変換して返すIteratorを作成してください。
要件:
filter
とmap
メソッドの組み合わせを活用。collect
メソッドで結果をベクタに収集。
例:
let result: Vec<_> = RangeFilter::new(1, 20)
.filter(|x| x % 5 == 0)
.map(|x| x * 2)
.collect();
println!("{:?}", result);
// 出力: [10, 20, 30, 40]
問題3: 複数のIteratorを組み合わせたデータ処理
データのリストから、以下の条件に基づいて処理を行うIteratorチェーンを構築してください:
- 負の値を取り除く。
- 各値を平方に変換。
- 合計を計算して返す。
要件:
- 入力は
Vec<i32>
。 - Iteratorチェーンを使って効率的に処理。
例:
let data = vec![-3, -2, 0, 1, 4];
let sum: i32 = DataProcessor::new(data)
.filter(|&x| x >= 0)
.map(|x| x * x)
.sum();
println!("{}", sum);
// 出力: 17
問題4: スライスから反復処理するIterator
任意のスライス(配列の一部)を操作するIteratorを実装してください。このIteratorは、スライスの要素を1つずつ返しますが、最後にスライスの長さも返すように設計してください。
要件:
- スライスとその長さを保持。
- イテレーションの最後に
Some("Length: N")
を返す。
例:
let slice = &[10, 20, 30];
let mut iter = SliceInfoIterator::new(slice);
while let Some(item) = iter.next() {
println!("{}", item);
}
// 出力:
// 10
// 20
// 30
// Length: 3
問題5: 任意の条件で終了するIterator
要素を生成し続けますが、生成された要素の合計が指定された上限値を超えた時点で終了するIteratorを実装してください。
要件:
- 内部で合計値を追跡。
- 合計が上限を超えたら
None
を返す。
例:
let capped_iter = CappedSumIterator::new(1.., 20);
for num in capped_iter {
println!("{}", num);
}
// 出力: 1, 2, 3, 4, 5
// (合計20で終了)
まとめ
演習問題に取り組むことで、Iterator
トレイトの実装や応用力を高めることができます。ぜひRustの開発環境で実際にコードを書き、試行錯誤を通じて理解を深めてください。
まとめ
本記事では、RustにおけるIterator
トレイトの基本概念から始まり、カスタムIteratorの作成方法や高度な応用例、さらにIteratorチェーンの活用方法までを解説しました。RustのIterator
は、データの効率的な処理とコードの簡潔化を可能にする非常に強力な機能です。
特に、以下の点を学びました:
Iterator
トレイトの基本構造と必要なメソッドの実装。- カスタムIteratorを作成する利点とその応用例。
- 標準Iteratorメソッドとの組み合わせによる柔軟なデータ処理パイプラインの構築。
- ライフタイムや所有権を考慮した安全で効率的なデザイン。
RustのIteratorを活用することで、単純な反復処理だけでなく、複雑なデータ処理やリアルタイムなデータストリーム操作も安全かつ簡潔に記述できます。この学びを基に、自分のプロジェクトで独自のIteratorを実装し、Rustのエコシステムを最大限に活用してください。
コメント