💤

rustでいちいちiterとcollectを書かない方法

2024/10/17に公開

Vectorを使う場合にmapやfilterをよく使うことになると思います。しかし、rustの場合は一旦iterを使ってiteratorを取得して、最後に明示的にcollectを実行して別のVectorを生成する必要があるようです。

例えばこんな感じ。

let v = vec![1, 2, 3, 4, 5];
let result = v.iter().filter(|i| *i % 2 == 0 ).collect::<Vec<_>>();
assert_eq!(result.len(), 2);
assert_eq!(*result[0], 2);
assert_eq!(*result[1], 4);

rubyやらkotlinやらに慣れていると随分と冗長に見えちゃいますね。正直レシーバが誰でどんなクロージャが渡されるのか以外には関心を持ちたくない部分なのにちょっとノイズが激しいように思います。しかも記号だらけでタイピングしにくいですね。

これをいちいち書かずに以下のようにできると嬉しいですね。

let v = vec![1, 2, 3, 4, 5];
let result = v.filter(|i| *i % 2 == 0 );
assert_eq!(result.len(), 2);
assert_eq!(*result[0], 2);
assert_eq!(*result[1], 4);

やりましょう。

Vectorを拡張しよう

rustでは現在作成中のcrate外部の型にメソッドを追加することはできないようですが、拡張機能として追加することなら可能なようです。具体的にはtraitを定義してそれを実装させることは可能ということになります。

Vectorにfilterメソッドを使いたいなら以下のようにいます。

pub trait VectorOmit<T> {
    fn filter<F>(&self, f: F) -> Vec<&T>
    where
        F: FnMut(&&T) -> bool;
}

impl<T> VectorOmit<T> for Vec<T> {
    fn filter<F>(&self, f: F) -> Vec<&T>
    where
        F: FnMut(&&T) -> bool,
    {
        self.iter().filter(f).collect::<Vec<&T>>()
    }
}

上記のようにすればVectorから直接filterを呼び出せます。

traitをuseしよう

「traitは定義しただけじゃ使えないぞ。ちゃんとuseしないと。」といった具合に定義しだけだと外部のmoduleからまだ使えません。
なので利用する際はtraiteをuseして使いましょう。useすると自動的に拡張機能が利用可能になります。

pub trait VectorOmit<T> {
    fn filter<F>(&self, f: F) -> Vec<&T>
    where
        F: FnMut(&&T) -> bool;
}

impl<T> VectorOmit<T> for Vec<T> {
    fn filter<F>(&self, f: F) -> Vec<&T>
    where
        F: FnMut(&&T) -> bool,
    {
        self.iter().filter(f).collect::<Vec<&T>>()
    }
}

#[cfg(test)]
mod test_vector_omit {

    use super::VectorOmit;

    #[test]
    fn test_filter() {
        let v = vec![1, 2, 3, 4, 5];
        let result = v.filter(|i| *i % 2 == 0 );
        assert_eq!(result.len(), 2);
        assert_eq!(*result[0], 2);
        assert_eq!(*result[1], 4);
    }
}

以上になります。

Discussion