Rustコトハジメ

プログラミング言語Rustに関する情報をお届けします。

IteratorのメソッドがFnMutをとるのはなぜでしょうね

Rustのイテレータ関数型プログラミングが出来るようになっていて、例としてmapfoldのような関数があります。

fn map<B, F>(self, f: F) -> Map<Self, F> where
    Self: Sized, F: FnMut(Self::Item) -> B,

fn fold<B, F>(mut self, init: B, mut f: F) -> B where
    Self: Sized, F: FnMut(B, Self::Item) -> B,

Mapなどはイテレータアダプタ(JavaのGuavaを知ってる人は、TransformedIteratorなどがこれに相当します)で実装されていて、これも別途記事にしたいと思いますが、今回は型に注目していきます。

私がこれを見てびっくりしたのは、mapfoldだけでなく、他の関数もすべてがFnMutを使っていることです。これはなぜでしょう。どう見ても、Fnしか必要ないように思うのですが。

FnMutにすることのメリット(というかデメリットといってもよさそうですが)としては、

fn main() {
    let mut sum = 0;
    let v1 = vec![1,2,3,4,5,6,7,8,9,10];
    let v2: Vec<i32> = v1.iter().map(|&x| {
        sum += x;
        x * 2
    }).collect();
    
    println!("sum={},v2={:?}", sum, v2);
}

のようなコードが書けてしまうということがありますが、

明らかなデメリットとしてはこのインターフェイスイテレータの処理が順々に行われることに依存しているということです。実際に、並列プログラミングをサポートするRayonではFnでした。

impl<I, F, R> ParallelIterator for Map<I, F>
where
    I: ParallelIterator,
    F: Fn(I::Item) -> R + Sync + Send,
    R: Send,

実用的に問題になることはないと思うのですが、例えばRustのコードを自動的に並列化するとかいうことを考えた時には足かせになるのかなぁと思いました。何か理由があってFnMutになったんでしょうか。