🦔

[rust] collect関数とは?

2024/12/31に公開2

rustで以下のコードを実行します。

 pub fn type_of<T>(_: &T) -> &'static str {
     std::any::type_name::<T>()
 }
 
fn main() {

    println!("Hello, world!");
    // let v = "abc1defXghiXabc".splitn(2, |c| c == '1' || c == 'X');
    let v: Vec<&str> = "abc1defXghi".splitn(2, |c| c == '1' || c == 'X').collect();
    
    print!("v={:?}\n", v);
    print!("typeof(v)={:?},", type_of(&v));
}

出力は以下のようになる

Hello, world!
v=["abc", "defXghi"]
typeof(v)="alloc::vec::Vec<&str>",

変数vの代入時の最後にcollect関数を実行しています。

  • splitnの動作:
    • "abc1defXghi"という文字列を、|c| c == '1' || c == 'X'という条件で最大2回分割します。この結果、splitnは次のようなイテレータを返します:
    • 最初の部分文字列: "abc"
    • 2番目の部分文字列: "defXghi"
  • collectの動作:
    • collectメソッドを呼び出すことで、splitnによって生成される部分文字列をすべて収集し、新しいコレクション(ここではVec<&str>型)に変換します。

[なぜcollectが必要か]

splitnの戻り値はイテレータであり、そのままではすべての結果を利用することができません。例えば、イテレータから1つずつ値を取り出す場合、次のように書く必要があります:

let mut iter = "abc1defXghi".splitn(2, |c| c == '1' || c == 'X');
println!("{:?}", iter.next()); // Some("abc")
println!("{:?}", iter.next()); // Some("defXghi")
println!("{:?}", iter.next()); // None

collectを使うことで、これらの値をまとめてコレクション(ここではVec<&str>)に格納できるため、より使いやすくなります。

[collectの処理を調査]

上記の関数からcollectを抜いて実行してみよう

 pub fn type_of<T>(_: &T) -> &'static str {
     std::any::type_name::<T>()
 }
 
fn main() {

    println!("Hello, world!");
    let v = "abc1defXghiXabc".splitn(2, |c| c == '1' || c == 'X');
    // let v: Vec<&str> = "abc1defXghi".splitn(2, |c| c == '1' || c == 'X').collect();
    
    print!("v={:?}\n", v);
    print!("typeof(v)={:?},", type_of(&v));
Hello, world!
v=SplitN(SplitNInternal { iter: SplitInternal { start: 0, end: 15, matcher: CharPredicateSearcher { haystack: "abc1defXghiXabc", char_indices: CharIndices { front_offset: 0, iter: Chars(['a', 'b', 'c', '1', 'd', 'e', 'f', 'X', 'g', 'h', 'i', 'X', 'a', 'b', 'c']) } }, allow_trailing_empty: true, finished: false }, count: 2 })
typeof(v)="core::str::iter::SplitN<playground::main::{{closure}}>",

変数vの中身が変になってる〜〜
よく見てみると、関数のようになっている。しかし関数ではない。
これはイテレータというものである。

  • イテレータとは、連続的な値を生成する仕組みである。

[なぜイテレータと関数は似ているのか?]

  • 関数は「入力 → 出力」の変換を1回行う。
  • イテレータは「現在の状態 → 次の値」を何度も繰り返します。
  • クロージャジェネリクスを利用:
    • イテレータはクロージャやジェネリクスを活用して動的な振る舞いをカプセル化しています。
  • Iteratorトレイト:
    • イテレータはトレイトIteratorを実装しており、next()メソッドを通じて値を順次返します。このメソッドはまるで「関数の呼び出し」のように動作します。
trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}
  • 遅延評価:
    • 必要な時点で次の値を計算するため、呼び出し可能な関数に似ています。

[イテレータの定義]

イテレータは「連続した要素を1つずつ順番に生成する構造」です。Rustでは、イテレータはIteratorトレイトを実装する型です。

[イテレータの特徴]

  • 遅延評価: イテレータは必要になるまで実行されません。たとえば、分割結果をすべて計算せず、1つずつ取り出すことができます。
    :::messsage
    今回を例にすると、splitnの戻り値がイテレータ型であるため、分割結果を一度にすべて計算するのではなく、必要なタイミングで逐次的に生成します。そのため、collectを呼び出さない限り、すべての結果を保存した状態にはなりません。
    :::
  • 軽量: メモリ効率がよく、データ構造をその場で生成するための計算コストが低いです。
  • 柔軟性: チェイン可能な多くのメソッド(例:map, filter, collect)を使って操作をカスタマイズできます。

まとめ

  • vは分割された文字列を逐次生成するイテレータ型SplitN。
  • イテレータは、関数のように次の値を計算して返す仕組みを持っています。
  • イテレータの利点として、遅延評価、メモリ効率、柔軟性が挙げられます。
  • collectメソッドを使うことで、イテレータの結果を完全なコレクションに変換できます。

Discussion

kanaruskanarus

出力は以下のようになる

の下、コピペミス (?) ですかね?

TommyTommy

いつもご指摘ありがとうございます。
ご指摘のとおり、コピペミスでした。
現在は修正しております。