はじめに
Rustを書いていると頻繁に呼び出す関数があります。
イテレータの最後に呼び出すcollect
関数です。
let filtered: Vec<i32> = (0..10).filter(|x| x % 2 == 0).collect();
println!("{:?}", filtered); // [0, 2, 4, 6, 8]
今までは深く考えずに呼び出していましたが
ふと内部の動作が気になったので、今回調べることにしました。
collect関数の内部
collect
関数はコアのソースコードのiterator.rs
に定義されています。
実装は以下の通りです。
fn collect<B: FromIterator<Self::Item>>(self) -> B
where
Self: Sized,
{
FromIterator::from_iter(self)
}
FromIterator
というトレイトの関数を呼び出しているだけのシンプルな処理でした。
次はこのFromIterator
トレイトについて説明していきます。
FromIterator
FromIterator
トレイトはイテレータから別の型に変換するためのトレイトです。
Vec
やHashMap
等はこのトレイトを実装しているのでcollect
関数で変換できます。
FromIterator
トレイトは、collect.rs
に以下の様に定義されています。
pub trait FromIterator<A>: Sized {
/// Creates a value from an iterator.
///
/// See the [module-level documentation] for more.
///
/// [module-level documentation]: crate::iter
///
/// # Examples
///
/// ```
/// let five_fives = std::iter::repeat(5).take(5);
///
/// let v = Vec::from_iter(five_fives);
///
/// assert_eq!(v, vec![5, 5, 5, 5, 5]);
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_diagnostic_item = "from_iter_fn"]
fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self;
}
上記にあるIntoIterator
トレイトとは、ある型からイテレータへの変換を行うトレイトです。
FromIterator
とは逆の変換を行うトレイトです。
実装例
実際にFromIterator
トレイトを実装してみましょう。
以下の様に整数コレクションの計算結果を格納するCalcResult
構造体を定義します。
sum
には整数の総和を、product
には総乗を格納します。
struct CalcResult {
pub sum: i32,
pub product: i32,
}
次に整数のイテレータからCalcResult
に変換するコードを書きます。
当然ですが、この時点ではコンパイルエラーになります。
// FromIteratorトレイトが実装されていないので変換できない
let result: CalcResult = (0..10).filter(|x| x % 2 == 1).collect();
println!("{} {}", result.sum, result.product);
CalcResult
構造体にFromIterator
トレイトを実装します。
fold
関数を使って総和と総乗を一度に計算します。
impl FromIterator<i32> for CalcResult {
fn from_iter<T: IntoIterator<Item = i32>>(iter: T) -> Self {
let result = iter
.into_iter()
.fold((0, 1), |result, x| (result.0 + x, result.1 * x));
let (sum, product) = result;
CalcResult { sum, product }
}
}
するとcollect
関数内で上記のfrom_iter
関数が呼び出されます。
これでコンパイルが通る様になり実行結果が表示されます。
25 945
総和は1 + 3 + 5 + 7 + 9 = 25
で、総乗は1 * 3 * 5 * 7 * 9 = 945
です。
このようにFromIterator
トレイトを実装すると、イテレータを独自の構造体に変換することが可能です。
コンパイルエラーの原因
collect
関数内の仕組みが分かると、なぜ型を明示しないとコンパイルエラーになるかが理解できます。
// このコードはコンパイルエラーになる
let filtered = (0..10).filter(|x| x % 2 == 0).collect();
collect
関数内でどの型のFromIterator::from_iter
関数を呼び出すべきかを
コンパイラが推論できないのでエラーになっていたということです。
以下の方法で型を明示する必要があります。
// 変数に型注釈をつける
let filtered: Vec<i32> = (0..10).filter(|x| x % 2 == 0).collect();
// collect関数に型引数を渡す
let filtered = (0..10).filter(|x| x % 2 == 0).collect::<Vec<i32>>();
参考文献