フィボナッチ数列は、Rustのイテレータ機能を学ぶ上で非常に適した題材です。そのアルゴリズムはシンプルでありながら、状態を持ちながら逐次計算を行うという点で、Iteratorトレイトの実装練習に最適です。ここでは、Rustらしさを感じられる方法でフィボナッチ数列を生成するイテレータを構築します。
コード例
まず、実装を見てみましょう。
struct FibonacciSequence(usize, usize);
impl FibonacciSequence {
fn start() -> Self {
FibonacciSequence(0, 1)
}
}
impl Iterator for FibonacciSequence {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
let current = self.0;
let next_value = self.1;
*self = FibonacciSequence(next_value, current + next_value);
Some(current)
}
}
fn main() {
let count = 15;
let result: Vec<_> = FibonacciSequence::start().take(count).collect();
println!("フィボナッチ数列(先頭{}項): {:?}", count, result);
}
このコードを実行すると、以下のような出力が得られます:
フィボナッチ数列(先頭15項): [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]
ポイントの解説
この実装には、Rustのいくつかの重要な概念が詰まっています。
1. タプル構造体の活用
FibonacciSequence(usize, usize)はタプル構造体です。内部に2つのusize値を持つだけで、フィールド名が必要ない場合は通常の構造体よりも簡潔に記述できます。これは、内部状態を隠蔽しつつ軽量に扱えるため、反復子の実装に適しています。
2. Iteratorトレイトの実装
カスタム型をイテレータとして振る舞わせるには、Iteratorトレイトを実装する必要があります。特に重要なのは以下の2点です:
type Item:イテレータが生成する要素の型を指定します。ここではusizeを使用しています。nextメソッド:各呼び出しで次の値を返すロジックを定義します。値がある限りSome(value)を、終了時はNoneを返します。
上記の実装では、現在の値を返した後、内部の状態を次のステップに更新しています。
3. 状態の更新における所有権の取り扱い
*self = FibonacciSequence(...)という書き方は、&mut self経由でインスタンス自体を再代入する方法です。Rustでは、selfが可変参照として渡されているため、その参照先のデータを直接上書きすることが可能です。これにより、新しいインスタンスが現在の状態を完全に置き換えます。
一方、誤ったアプローチとして以下のようなコードがあります:
// コンパイルエラーになる例
self = &mut FibonacciSequence(self.1, self.0 + self.1); // ❌
この場合、右辺の&mut Fib(...)は一時的なオブジェクトへの参照を作ろうとしており、式の評価後に破棄されるため、selfが無効な参照を指すことになります。Rustの借用チェッカーはこれを検出し、コンパイル時にエラーを発生させます。
4. collectと型注釈
take(n)で最初のn個の要素を取り出した後、collect()でコレクションに変換します。このとき、collectはどの型に集約するか明示しないとコンパイラが推論できないことがあります。そのため、以下のように型注釈を追加します:
let result: Vec<_> = FibonacciSequence::start().take(10).collect();
または、collect::<Vec<usize>>()のように明示的に型を指定しても構いません。こうすることで、マクロ内での使用時でも正しく型が解決されます。