RustでIteratorの拡張メソッドを自作してみる
対象読者層
- Rustの初学者
- Rustの文法をふんわり理解している
- mapやfilterのようなイテレータをふんわり使える
環境
- Rust 1.83.0
- edtion 2021
背景
Rustの標準のscan
の作りがあまり好みではないので、勉強も兼ねて自作してみた。
scanとは
一言で言うと「 "処理ごとのスナップショット" 付きのfold」。
HaskellやScalaには標準で用意されている。
Scalaだとこんな感じ。
@main
def main(): Unit =
val v = ('b' to 'd').scanLeft("a")((state, element) => state + element)
println(v)
Vector(a, ab, abc, abcd)
記述はfold
と似ているが、fold
で同じことをやると最後のabcd
のみが返ってくる。
scan
だと畳み込みの過程 (ab
やabc
) を保持したリストが返ってくる。
リストの最後はfold
したときと同じ値。
リストの先頭には初期値 (a
) がそのまま入る。
Rustのscan
調べたらRustにもscan
があったので似たようなものかと思ったら、Scala版とは結構違った。
Rustのscan
の定義はこう。
fn scan<St, B, F>(self, initial_state: St, f: F) -> Scan<Self, St, F>
where
Self: Sized,
F: FnMut(&mut St, Self::Item) -> Option<B>,
使用例はこう。
fn main() {
let v: Vec<String> = ('b'..='d')
.scan("a".to_string(), |state, element| {
state.push(element);
Some(state.clone())
})
.collect();
println!("{:?}", v);
}
["ab", "abc", "abcd"]
Scala版との主な違い
Scala | Rust | |
---|---|---|
引数の関数の型 | (S, A) -> A | (&mut S, A) -> Option<B> |
結果のリストの型 | [S] | [B] |
初期値(S)が結果の リストに入るか |
入る (先頭) |
入らない (SとBで型が異なるため入れられない) |
S
= 初期状態の型
A
= 元のリストの要素の型
[T]
= 型Tの要素を持つリスト
Scala版はシンプルに「前の状態 (S
) と次の要素 (A
) から新しい状態 (S
) を作る」ことに特化しており、各フェーズの状態をスナップショットとして逐一保持したリストを生成する。
一方、Rust版はもっと高機能だ。
初期stateは&mut
で引数のクロージャに渡され、処理が進むごとに変更操作 (先の例だとpush
) が繰り返される。
また、状態 (S
) と処理結果(B
) の型は異なっていてもよい。
なのでこんな書き方もできる。
fn main() {
let v: Vec<usize> = ('b'..='d')
.scan("a".to_string(), |state: &mut String, element: char| {
state.push(element);
// stateはStringのまま
println!("{}", state);
// 結果のリストの要素はusize
Some(state.len())
})
.collect();
println!("{:?}", v);
}
ab
abc
abcd
[2, 3, 4]
更にクロージャの戻り値の型は B
ではなく Option<B>
になっている。
関数内で Some(B)
ではなく None
を返すとそこでシーケンス処理が打ち切られる (次回の next
が None
を返す)。
Rust版もまあ納得はできるが、正直「面倒くさいな」と思った。
もっと機能を削ぎ落としていいから、Scalaみたくシンプルに書きたい。
お気持ちとしてはこんな感じ。
- ただただ各フェーズの状態のスナップショットだけ欲しい。
- あと初期状態をリストの先頭に入れておきたい。
ということで作ってみる。
イメージとしてはこんな動作をしてほしい。
関数名はScalaに合わせて scan_left
にする。
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scan_left() {
let v: Vec<String> = ('b'..='d')
.scan_left("a".to_string(), |s, ch| format!("{}{}", s, ch))
.collect();
assert_eq!(
v,
vec![
"a".to_string(),
"ab".to_string(),
"abc".to_string(),
"abcd".to_string()
]
);
}
}
既存のscanを読む
まずは既存の scan
関数を読んで参考にする。
ソースはこちら。
#[inline]
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_do_not_const_check]
fn scan<St, B, F>(self, initial_state: St, f: F) -> Scan<Self, St, F>
where
Self: Sized,
F: FnMut(&mut St, Self::Item) -> Option<B>,
{
Scan::new(self, initial_state, f)
}
型パラメータSt
が状態の型。
B
が引数のクロージャの戻り値の型 (正確にはOption
に詰める型)。
F
がクロージャのtrait境界。
Scan
構造体の型パラメータとして使用しているため、Self
にはSized
制約がついている。
ここでやってることはScan
構造体に自分 (Iterator) と引数を詰め込んでるだけ。
ということは使用感からして、Scan
がIterator
を実装している作りになっているはず。
構造体の方を見てみる。
#[must_use = "iterators are lazy and do nothing unless consumed"]
#[stable(feature = "rust1", since = "1.0.0")]
#[derive(Clone)]
pub struct Scan<I, St, F> {
iter: I,
f: F,
state: St,
}
impl<I, St, F> Scan<I, St, F> {
pub(in crate::iter) fn new(iter: I, state: St, f: F) -> Scan<I, St, F> {
Scan { iter, state, f }
}
}
Scan
構造体は以下の3つのフィールドを持つ。
- iter: 元の
Iterator
- f: 状態変更用のクロージャ
- state: 現在の状態
面白いことにそれぞれの型はジェネリックになってはいるが、構造体の定義時点では特にトレイト境界は設定されていない。
なのでこの定義のみに則るのであればiter
はIterator
でなくともよく、f
も関数でなくともよい。
// こういうScanもありえる?
let s = Scan {
iter: "hoge",
f: 42,
state: vec![]
};
しかし実際はこのようには書けない。
各フィールドはprivate
だし、new
関数もcore::iter
モジュール内でしか使えない。
なので我々ユーザはscan
関数を通さないとScan
のインスタンスを得ることができない。
そしてscan
関数にはちゃんと型制約がついているので問題ない、という仕組み (ここのSelf
はIterator
)。
fn scan<St, B, F>(self, initial_state: St, f: F) -> Scan<Self, St, F>
where
Self: Sized,
F: FnMut(&mut St, Self::Item) -> Option<B>,
{
Scan::new(self, initial_state, f)
}
Scan
構造体の定義を下にスクールするとIterator
の実装がある。
#[stable(feature = "rust1", since = "1.0.0")]
impl<B, I, St, F> Iterator for Scan<I, St, F>
where
I: Iterator,
F: FnMut(&mut St, I::Item) -> Option<B>,
{
type Item = B;
#[inline]
fn next(&mut self) -> Option<B> {
let a = self.iter.next()?;
(self.f)(&mut self.state, a)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let (_, upper) = self.iter.size_hint();
(0, upper) // can't know a lower bound, due to the scan function
}
#[inline]
fn try_fold<Acc, Fold, R>(&mut self, init: Acc, fold: Fold) -> R
size_hint
とtry_fold
は一旦無視。
注目すべきはnext
の実装。
#[stable(feature = "rust1", since = "1.0.0")]
impl<B, I, St, F> Iterator for Scan<I, St, F>
where
I: Iterator,
F: FnMut(&mut St, I::Item) -> Option<B>,
{
type Item = B;
#[inline]
fn next(&mut self) -> Option<B> {
let a = self.iter.next()?;
(self.f)(&mut self.state, a)
}
特に難しいことはしていない。
内部のIterator
から次の要素を取り出してクロージャを当ててるだけ。
とりあえずこんな感じでnext
を実装すれば良さそう。
自作Scanの定義
元のScan
を参考に自作していく。
まず、やりたいことは以下の2つだった。
- ただただ各フェーズの状態のスナップショットだけ欲しい。
- あと初期状態をリストの先頭に入れておきたい。
想定動作はこうだった。
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scan_left() {
let v: Vec<String> = ('b'..='d')
.scan_left("a".to_string(), |s, ch| format!("{}{}", s, ch))
.collect();
assert_eq!(
v,
vec![
"a".to_string(),
"ab".to_string(),
"abc".to_string(),
"abcd".to_string()
]
);
}
}
これは実際には以下と同義。
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scan_left() {
let mut it = ('b'..='d').scan_left("a".to_string(), |s, ch| format!("{}{}", s, ch));
assert_eq!(it.next(), Some("a".to_string()));
assert_eq!(it.next(), Some("ab".to_string()));
assert_eq!(it.next(), Some("abc".to_string()));
assert_eq!(it.next(), Some("abcd".to_string()));
assert_eq!(it.next(), None);
}
}
先に確認した既存のscan
の動きを参考しつつ、今回やりたいことのTODOリストを作る。
-
Iterator
を実装する構造体
ScanLeft`を作る-
初回の
next
: 初期状態 (と同値となるもの) をそのまま返す -
2回目以降の
next
: 現在の状態と次の要素から生成された新しい状態を返す。次の要素がNone
ならNone
を返す
-
初回の
-
既存の
Iterator
から呼び出せるscan_left: (state, f) -> ScanLeft
関数を作る
とりあえず今はこれだけ出来ればよさそう。
早速構造体の方から作っていく。
構造体の作成
scan_left.rs
というファイルを作り、その中にScanLeft
構造体を定義する。
#[derive(Clone)]
pub struct ScanLeft<I, St, F> {
iter: I,
f: F,
state: St,
}
元のScan
と同様、この時点では型制約はかけないことにする。
新しいscan_left
関数を呼んだら呼び出し側にこの構造体が返ってくる想定なのでpub
をつける。
またIterator
のcloned
が使えるようにClone
traitを実装しておく。
次に、ScanLeft
にIterator
を実装する。
ここで構造体のI
およびF
に型制約をかける。
impl<I, St, F> Iterator for ScanLeft<I, St, F>
where
I: Iterator,
F: Fn(&St, I::Item) -> St,
{
type Item = St;
fn next(&mut self) -> Option<Self::Item> {
todo!()
}
}
初回のnext実装
まずは初回のnext
が初期状態 (と同値となるもの) をそのまま返すようにする。
検証用のテストを書いて、落ちることを確認する。
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn first_next_should_return_initial_state() {
let mut sut = ScanLeft {
iter: 'b'..='d',
state: "a".to_string(),
f: |s: &String, ch: char| format!("{}{}", s, ch).to_string(),
};
assert_eq!(sut.next(), Some("a".to_string()));
}
}
構造体に型制約をつけていないので、このテストのように構造体を直接生成するならf
の引数には型注釈が必要。
将来的にはscan_left
から生成するので型注釈はいらなくなる予定。
この時点ではテストは当然落ちる。
$ cargo test
(中略)
failures:
scan_left::first_next_should_return_initial_state
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--bin scan_left`
とりあえずもらったstate
をそのまま返して無理やりテストを通す。
impl<I, St, F> Iterator for ScanLeft<I, St, F>
where
I: Iterator,
F: Fn(&St, I::Item) -> St,
{
type Item = St;
fn next(&mut self) -> Option<St> {
Some(self.state) // cannot move
}
}
これは cannot move
となりコンパイルできない。
error[E0507]: cannot move out of `self.state` which is behind a mutable reference
--> src/scan_left.rs:16:14
|
16 | Some(self.state)
| ^^^^^^^^^^ move occurs because `self.state` has type `St`, which does not implement the `Copy` trait
|
help: if `St` implemented `Clone`, you could clone the value
--> src/scan_left.rs:8:9
|
8 | impl<I, St, F> Iterator for ScanLeft<I, St, F>
| ^^ consider constraining this type parameter with `Clone`
...
16 | Some(self.state)
| ---------- you could clone this value
St
にCopy
かClone
制約をつけてあげればいいよと教えてくれる。
St
の型制約にClone
を追加し、state
のクローンを返すように修正する。
impl<I, St, F> Iterator for ScanLeft<I, St, F>
where
St: Clone, // Clone制約を追加
I: Iterator,
F: Fn(&St, I::Item) -> St,
{
type Item = St;
fn next(&mut self) -> Option<St> {
Some(self.state.clone()) // cloneを返すよう修正
}
}
これでテストが通るようになった。
$ cargo test
(中略)
running 1 test
test scan_left::first_next_should_return_initial_state ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
-
Iterator
を実装する構造体
ScanLeft`を作る-
初回の
next
: 初期状態 (と同値となるもの) をそのまま返す -
2回目以降の
next
: 現在の状態と次の要素から生成された新しい状態を返す。次の要素がNone
ならNone
を返す
-
初回の
-
既存の
Iterator
から呼び出せるscan_left: (state, f) -> ScanLeft
関数を作る
2回目以降のnext実装
先の実装では当然、2回目以降のnext
が正しい値を返さない。
テストを追加して、本来望む動きになるようにする。
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn next_should_return_folded_values() {
let mut sut = ScanLeft {
iter: 'b'..='d',
state: "a".to_string(),
f: |s: &String, ch: char| format!("{}{}", s, ch).to_string(),
};
assert_eq!(sut.next(), Some("a".to_string()));
assert_eq!(sut.next(), Some("ab".to_string()));
assert_eq!(sut.next(), Some("abc".to_string()));
assert_eq!(sut.next(), Some("abcd".to_string()));
assert_eq!(sut.next(), None);
}
}
$ cargo test
(中略)
running 2 tests
test scan_left::first_next_should_return_initial_state ... ok
test scan_left::next_should_return_folded_values ... FAILED
failures:
---- scan_left::next_should_return_folded_values stdout ----
thread 'scan_left::next_should_return_folded_values' panicked at src/scan_left.rs:41:5:
assertion `left == right` failed
left: Some("a")
right: Some("ab")
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
scan_left::next_should_return_folded_values
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
まず、初回実行と2回目以降の実行を判別できるようにしないといけない。
ScanLeft
構造体にstarted
フラグを追加する。
#[derive(Clone)]
pub struct ScanLeft<I, St, F> {
iter: I,
state: St,
f: F,
started: bool, // 追加
}
これだとテストのコンパイルが通らなくなるので、構造体生成時にfalseを指定する。
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn first_next_should_return_initial_state() {
let mut sut = ScanLeft {
iter: 'b'..='d',
state: "a".to_string(),
f: |s: &String, ch: char| format!("{}{}", s, ch).to_string(),
started: false, // 追加
};
assert_eq!(sut.next(), Some("a".to_string()));
}
#[test]
fn next_should_return_folded_values() {
let mut sut = ScanLeft {
iter: 'b'..='d',
state: "a".to_string(),
f: |s: &String, ch: char| format!("{}{}", s, ch).to_string(),
started: false, // 追加
};
assert_eq!(sut.next(), Some("a".to_string()));
assert_eq!(sut.next(), Some("ab".to_string()));
assert_eq!(sut.next(), Some("abc".to_string()));
assert_eq!(sut.next(), Some("abcd".to_string()));
assert_eq!(sut.next(), None);
}
}
2回目以降のテストは相変わらず落ちるが、初回限定のテストはこれでもちゃんと通る。
$ cargo test
(中略)
running 2 tests
test scan_left::first_next_should_return_initial_state ... ok
test scan_left::next_should_return_folded_values ... FAILED
next
内でこのフラグを確認し、初回実行と2回目以降で処理を切り替えられるようにする。
impl<I, St, F> Iterator for ScanLeft<I, St, F>
where
St: Clone,
I: Iterator,
F: Fn(&St, I::Item) -> St,
{
type Item = St;
fn next(&mut self) -> Option<St> {
if !self.started {
// 初回実行
self.started = true;
Some(self.state.clone())
} else {
// 2回目以降
todo!()
}
}
}
2回目以降は元のIterator
を進めて、現在の状態と一緒にクロージャに渡す。
更新された状態が返ってくるのでそれを次の状態としつつ、clone
してその時点のスナップショットを返す。
元のIteratror
を消費しきったら終わり。
impl<I, St, F> Iterator for ScanLeft<I, St, F>
where
St: Clone,
I: Iterator,
F: Fn(&St, I::Item) -> St,
{
type Item = St;
fn next(&mut self) -> Option<St> {
if !self.started {
// 初回実行
self.started = true;
Some(self.state.clone())
} else {
// 2回目以降
if let Some(next) = self.iter.next() {
self.state = (self.f)(&self.state, next);
Some(self.state.clone())
} else {
None
}
}
}
}
これで2回目以降の呼び出しもテストが通るようになった。
$ cargo test
(中略)
running 2 tests
test scan_left::first_next_should_return_initial_state ... ok
test scan_left::next_should_return_folded_values ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
2つ目のテストに内包されているので、もう上の最初の呼び出し限定のテストはいらない。
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn next_should_return_folded_states() {
let mut sut = ScanLeft {
iter: 'b'..='d',
state: "a".to_string(),
f: |s: &String, ch: char| format!("{}{}", s, ch).to_string(),
started: false,
};
assert_eq!(sut.next(), Some("a".to_string()));
assert_eq!(sut.next(), Some("ab".to_string()));
assert_eq!(sut.next(), Some("abc".to_string()));
assert_eq!(sut.next(), Some("abcd".to_string()));
assert_eq!(sut.next(), None);
}
}
?
オペレータを使うともう少しスッキリ書ける。
impl<I, St, F> Iterator for ScanLeft<I, St, F>
where
St: Clone,
I: Iterator,
F: Fn(&St, I::Item) -> St,
{
type Item = St;
fn next(&mut self) -> Option<St> {
if !self.started {
// 初回実行
self.started = true;
} else {
// 2回目以降
let a = self.iter.next()?;
(self.f)(&mut self.state, a);
}
Some(self.state.clone())
}
}
&mut
にする
更新用クロージャの引数をこれで一応Scala版と似たようなことはできるようになった。
ただもう少し自然になるよう修正する。
先程までのテストだとformat!
マクロを使っているため目立ちにくいが、今の作りだと引数のクロージャの中で、元の状態をクローンして新しい状態を作ることになる。
試しにstateをString
からVec<char>
に変えてみる。
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn next_should_return_folded_states() {
let mut sut = ScanLeft {
iter: 'b'..='d',
state: vec!['a'],
f: |s: &Vec<char>, ch: char| {
let mut s = s.clone(); // クローンの取得
s.push(ch); // 更新操作
s // 返却
},
started: false,
};
assert_eq!(sut.next(), Some(vec!['a']));
assert_eq!(sut.next(), Some(vec!['a', 'b']));
assert_eq!(sut.next(), Some(vec!['a', 'b', 'c']));
assert_eq!(sut.next(), Some(vec!['a', 'b', 'c', 'd']));
assert_eq!(sut.next(), None);
}
}
こんな風に、クローンの取得 → 更新操作 → 返却 を各行に記述する必要がある。
Scalaで同じことをする場合、下のように1行で済む。
@main
def main(): Unit =
val v = ('b' to 'd').scanLeft(Vector('a'))((s, ch) => s.appended(ch))
assert(v == Vector(
Vector('a'),
Vector('a', 'b'),
Vector('a', 'b', 'c'),
Vector('a', 'b', 'c', 'd')
))
これは両言語の思想の違いもある。
ScalaのVector
は不変クラスなので、追加操作 (appended) は要素を追加した新しいVector
を返す。
Scalaは可変/不変をクラスレベルで管理しており、なるべく不変クラスを使い、可変操作を避ける傾向にある。
対してRustは shared XOR mutable
の原則および mut
キーワードの有無で可変性を管理している。
これによって責任が明確になるため、Scalaほど可変操作自体を忌避する必要はない。
無理せず必要に応じて使えばいい。
状態を可変参照を受け取って変更するようにすると、以下のような使い方になる。
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn next_should_return_folded_states() {
let mut sut = ScanLeft {
iter: 'b'..='d',
state: vec!['a'],
// 可変参照を受け取り内部で更新
f: |s: &mut Vec<char>, ch: char| s.push(ch),
started: false,
};
assert_eq!(sut.next(), Some(vec!['a']));
assert_eq!(sut.next(), Some(vec!['a', 'b']));
assert_eq!(sut.next(), Some(vec!['a', 'b', 'c']));
assert_eq!(sut.next(), Some(vec!['a', 'b', 'c', 'd']));
assert_eq!(sut.next(), None);
}
}
f
は単に状態を更新するだけの関数となり、戻り値はUnitとなる。
コンパイルが通るように実装を修正する。
impl<I, St, F> Iterator for ScanLeft<I, St, F>
where
St: Clone,
I: Iterator,
F: FnMut(&mut St, I::Item), // 可変参照を受け取るようにする
{
type Item = St;
fn next(&mut self) -> Option<St> {
if !self.started {
self.started = true;
} else {
let a = self.iter.next()?;
// クロージャ内でstateを更新させる
(self.f)(&mut self.state, a);
}
// (2回目以降は) この時点で新しいstateに更新されている
Some(self.state.clone())
}
}
これでRustらしい作りになった気がする。
完成版は下記の通り。
#[derive(Clone)]
pub struct ScanLeft<I, St, F> {
iter: I,
state: St,
f: F,
started: bool,
}
impl<I, St, F> Iterator for ScanLeft<I, St, F>
where
St: Clone,
I: Iterator,
F: FnMut(&mut St, I::Item),
{
type Item = St;
fn next(&mut self) -> Option<St> {
if !self.started {
// 初回実行
self.started = true;
} else {
// 2回目以降
let a = self.iter.next()?;
(self.f)(&mut self.state, a);
}
Some(self.state.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn next_should_return_folded_values() {
let mut sut = ScanLeft {
iter: 'b'..='d',
state: vec!['a'],
f: |s: &mut Vec<char>, ch: char| s.push(ch),
started: false,
};
assert_eq!(sut.next(), Some(vec!['a']));
assert_eq!(sut.next(), Some(vec!['a', 'b']));
assert_eq!(sut.next(), Some(vec!['a', 'b', 'c']));
assert_eq!(sut.next(), Some(vec!['a', 'b', 'c', 'd']));
assert_eq!(sut.next(), None);
}
}
-
Iterator
を実装する構造体
ScanLeft`を作る-
初回の
next
: 初期状態 (と同値となるもの) をそのまま返す -
2回目以降の
next
: 現在の状態と次の要素から生成された新しい状態を返す。次の要素がNone
ならNone
を返す
-
初回の
-
既存の
Iterator
から呼び出せるscan_left: (state, f) -> ScanLeft
関数を作る
呼び出し用の拡張メソッドの作成
モジュール外からScanLeft
を生成、使用できるように、外部向けの口を作ってあげる。
欲しいものは元のIterator
、初期状態、更新用クロージャ の3つ。
下記のように外部モジュールから参照、実行できればよい。
#[cfg(test)]
mod tests {
#[test]
fn test_scan_left() {
let mut sut = (1..=4).scan_left(0, |s, ch| *s += ch);
assert_eq!(sut.next(), Some(0));
assert_eq!(sut.next(), Some(1));
assert_eq!(sut.next(), Some(3));
assert_eq!(sut.next(), Some(6));
assert_eq!(sut.next(), Some(10));
assert_eq!(sut.next(), None);
}
}
Iterator
の拡張関数にする前に、まず普通の関数として定義してみる。
型制約はScanLeft
のIterator
実装と同じものにする。
pub fn scan_left<I, St, F>(iter: I, initial_state: St, f: F) -> ScanLeft<I, St, F>
where
St: Clone,
I: Iterator,
F: FnMut(&mut St, I::Item),
{
ScanLeft {
iter,
state: initial_state,
f,
started: false, // 最初は必ずfalse
}
}
一応、もうこれで動くようにはなる。
#[cfg(test)]
mod tests {
#[test]
fn test_scan_left() {
let mut sut = scan_left(1..=4, 0, |s, ch| *s += ch);
assert_eq!(sut.next(), Some(0));
assert_eq!(sut.next(), Some(1));
assert_eq!(sut.next(), Some(3));
assert_eq!(sut.next(), Some(6));
assert_eq!(sut.next(), Some(10));
assert_eq!(sut.next(), None);
}
}
ただどうせならIterator
の拡張メソッドにしてしまった方が、メソッドチェーンができて書き味が良い。
ということでそうしてみる。
拡張メソッドの作り方
まず新たにmy_iter.rs
ファイルを作成し、Iterator
をスーパートレイトに持つMyIterator
を定義する。
ついでにscan_left
に発展する前に、何かわかりやすいメソッドを一つ作っておく (あとで消す)。
pub trait MyIterator: Iterator {
// 何か適当なメソッド
fn hello(&self) {
println!("hello");
}
}
このままだと既存のIterator
(Range<i32>
とかIntoIter<char>
とか諸々) からhello
を呼び出せない。
なのでジェネリクスを使い、MyIterator
を使いたい全ての既存Iterator
に対応するようにする。
pub trait MyIterator: Iterator {
fn hello(&self) {
println!("hello");
}
}
// 全ての既存Iteratorがhelloを呼び出せるようにする
impl<I: Iterator> MyIterator for I {}
これでどのIterator
からもhello
が呼べるようになった。
use std::ops::Range;
use std::vec::IntoIter;
pub trait MyIterator: Iterator {
fn hello(&self) {
println!("hello");
}
}
impl<I: Iterator> MyIterator for I {}
fn main() {
let iter1: Range<i32> = 0..10;
let iter2: IntoIter<char> = vec!['1'].into_iter();
iter1.hello(); // hello
iter2.hello(); // hello
}
これはやってることとしては以下と同義。
Rustのジェネリクスは、それを使用している型の数だけコンパイル時にモノモーフィック化される。
pub trait MyIterator: Iterator {
fn hello(&self) {
println!("hello");
}
}
// impl<I: Iterator> MyIterator for I {}
// 今回の使用例に限っては以下と同義
impl MyIterator for Range<i32> {}
impl MyIterator for IntoIter<char> {}
scan_leftの拡張メソッド化
あとはhello
をscan_left
に置き換えればおしまい。
元のscan_left
はこうなっていた。
pub fn scan_left<I, St, F>(iter: I, initial_state: St, f: F) -> ScanLeft<I, St, F>
where
St: Clone,
I: Iterator,
F: FnMut(&mut St, I::Item),
{
ScanLeft {
iter,
state: initial_state,
f,
started: false, // 最初は必ずfalse
}
}
これをmy_iter.rs
内のMyIterator
のメソッドとして移行する。
MyIterator
はIterator
なので、上記の型パラメータI
はSelf
となる。
use crate::scan_left::ScanLeft;
pub trait MyIterator: Iterator {
fn scan_left<St, F>(self, initial_state: St, f: F) -> ScanLeft<Self, St, F>
where
Self: Sized,
St: Clone,
F: FnMut(&mut St, Self::Item),
{
ScanLeft {
iter: self,
state: initial_state,
f,
started: false,
}
}
}
impl<I: Iterator> MyIterator for I {}
ただしこれだとまだコンパイルが通らない。
ScanLeft
の各フィールドはprivate
なので、別モジュール(my_iter
)に移した今はアクセスできなくなった。
scan_left
モジュール側に、外部からScanLeft
を生成できるファクトリー関数を作成する。
#[derive(Clone)]
pub struct ScanLeft<I, St, F> {
iter: I,
state: St,
f: F,
started: bool,
}
impl<I, St, F> ScanLeft<I, St, F> {
// ファクトリー関数を定義
pub fn new(iter: I, state: St, f: F) -> Self {
Self {
iter,
state,
f,
// startedは最初は必ずfalseなのでここで指定
started: false,
}
}
}
my_iter
モジュールからはこのファクトリー関数を使うよう修正する。
use crate::scan_left::ScanLeft;
pub trait MyIterator: Iterator {
fn scan_left<St, F>(self, initial_state: St, f: F) -> ScanLeft<Self, St, F>
where
Self: Sized,
St: Clone,
F: FnMut(&mut St, Self::Item),
{
ScanLeft::new(self, initial_state, f)
}
}
impl<I: Iterator> MyIterator for I {}
これでコンパイルは通るようになった。
しかしScanLeft::new
はmy_iter
モジュールからしか使わないので、pub
で宣言するのはいささかスコープが広すぎる。
スコープを絞るために、scan_left
モジュールをmy_iter
モジュールの子モジュールにする。
ファイル構成としては以下のようになる。
├── src
│ ├── lib.rs
│ ├── my_iter
│ │ └── scan_left.rs
│ └── my_iter.rs
scan_left
モジュールが子モジュールになったので、親のmy_iter
モジュール側に宣言を追加する
use crate::my_iter::scan_left::ScanLeft;
// 子モジュールを紐付け
mod scan_left;
pub trait MyIterator: Iterator {
fn scan_left<St, F>(self, initial_state: St, f: F) -> ScanLeft<Self, St, F>
where
Self: Sized,
St: Clone,
F: FnMut(&mut St, Self::Item),
{
ScanLeft::new(self, initial_state, f)
}
}
impl<I: Iterator> MyIterator for I {}
こうすればscan_left
モジュール自体は外から見えないし、ScanLeft::new
関数の呼び出し範囲もmy_iter
モジュール以下の階層に絞ることができる。
impl<I, St, F> ScanLeft<I, St, F> {
// my_iterモジュール以下の階層でのみ呼び出し可能
pub(in crate::my_iter) fn new(iter: I, state: St, f: F) -> Self {
Self {
iter,
state,
f,
started: false,
}
}
}
これでユーザはmy_iter
モジュールを通してのみScanLeft
を使えるようになり、scan_left
の拡張メソッド化は完了。
以下のように元々のscan
と同じような記法で書けるようになった。
mod my_iter;
#[cfg(test)]
mod tests {
use crate::my_iter::MyIterator;
#[test]
fn test_scan_left() {
let mut sut = (1..=4).scan_left(0, |s, ch| *s += ch);
assert_eq!(sut.next(), Some(0));
assert_eq!(sut.next(), Some(1));
assert_eq!(sut.next(), Some(3));
assert_eq!(sut.next(), Some(6));
assert_eq!(sut.next(), Some(10));
assert_eq!(sut.next(), None);
}
}
-
Iterator
を実装する構造体
ScanLeft`を作る-
初回の
next
: 初期状態 (と同値となるもの) をそのまま返す -
2回目以降の
next
: 現在の状態と次の要素から生成された新しい状態を返す。次の要素がNone
ならNone
を返す
-
初回の
-
既存の
Iterator
から呼び出せるscan_left: (state, f) -> ScanLeft
関数を作る
まとめ
まだまだ作り込む要素はありそうだけど、一旦今回はここまで。
改めて、今回作成したモジュールは以下の2つ。
├── src
│ ├── my_iter
│ │ └── scan_left.rs
│ └── my_iter.rs
use crate::my_iter::scan_left::ScanLeft;
mod scan_left;
pub trait MyIterator: Iterator {
fn scan_left<St, F>(self, initial_state: St, f: F) -> ScanLeft<Self, St, F>
where
Self: Sized,
St: Clone,
F: FnMut(&mut St, Self::Item),
{
ScanLeft::new(self, initial_state, f)
}
}
impl<I: Iterator> MyIterator for I {}
#[derive(Clone)]
pub struct ScanLeft<I, St, F> {
iter: I,
state: St,
f: F,
started: bool,
}
impl<I, St, F> ScanLeft<I, St, F> {
pub(in crate::my_iter) fn new(iter: I, state: St, f: F) -> Self {
Self {
iter,
state,
f,
started: false,
}
}
}
impl<I, St, F> Iterator for ScanLeft<I, St, F>
where
St: Clone,
I: Iterator,
F: FnMut(&mut St, I::Item),
{
type Item = St;
fn next(&mut self) -> Option<St> {
if !self.started {
// 初回実行
self.started = true;
} else {
// 2回目以降
let a = self.iter.next()?;
(self.f)(&mut self.state, a);
}
Some(self.state.clone())
}
}
Rustでイテレータや拡張メソッドをどう扱うかの理解が深まり勉強になった。
今後もちょくちょく使う機会はありそう。
Discussion