やっつけな日常(46) Rustに入ればRustに従え、イテレータで使えるメソッドその2

Joseph Halfmoon

前回からイテレータを勉強してます。前回は残念なことに直なメソッドばかりで紛糾?しなかったです。今回から紛糾しそうなメソッドに入っていきたいと思います。その入口は count() とな。実行すると結果を返してくれるけれどイテレータを「消費」してしまうメソッドです。そのままでは意味ないけれど2度数えることはできないの?<訂正あり>

※2022年11月28日訂正: 1.10をstd::iterのごとくに使用してますが、1..10はstd::ops::Range です。共通するお名前で同様な機能のTraitsが実装されているのでイテレータではないのにイテレータとして練習してしまいました。以下コード中 1..10の代わりに[1, 2, 3, 4, 5, 6, 7, 8, 9].iter()とすれば「正式な」イテレータになるんでないの、と思われます。

※「やっつけな日常」投稿順 Index はこちら

参照させていただいておりますRustのドキュメントは以下です。

Trait std::iter::Iterator

案の定なのだけれども、イテレータにもRustの「安全」なメモリ管理、所有権とかが登場してくるのね。忘却力の年寄はうっかりミス多数だけれども、コンパイラが後ろで監視してくれていると思えば安心?エラーのご指摘煩いけれども。。。

今回実験用のソースコード全文

今回実験は、イテレータを「消費」してしまうcount()メソッドで「2回数える」という実用にはならなそうなコードです。列挙するとこんな感じ。よく分からんな。

  1. rangeオブジェクトに対して2回目作用させてエラーになる
  2. rangeオブジェクトをby_refしておいて1回目、元のオブジェクトで2回目数える
  3. rangeオブジェクトをcloneしておいて2回目数える
  4. 配列をイテレータ化してclonedしてcollectでVec型にして、元の配列とVecのそれぞれで数えて合計2回
  5. 配列をイテレータ化してcopiedしてcollectでVec型にして、元の配列とVecのそれぞれで数えて合計2回

全文が以下に

fn main() {
    //count again
    let tgt = 1..10;
    println!("count0_1: {}", tgt.count());
    // println!("count2: {}", tgt.count()); <== ERROR!
    //by_ref
    let mut tgt1 = 1..10;
    let tgt1_clone = tgt1.clone();
    println!("count1_1.by_ref: {}", tgt1.by_ref().count());
    println!("count1_2: {}", tgt1.count());
    println!("count1_3 clone: {}", tgt1_clone.count());
    //cloned.collect()
    let tgt_a = [1, 2, 3, 4, 5, 6, 7, 8, 9];
    let mut tgt_avec : Vec<_> = tgt_a.iter().cloned().collect();
    tgt_avec[1]=999;
    println!("count2_1.cloned: {}", tgt_avec[1]);
    println!("count2_1 count : {}", tgt_avec.iter().count());
    println!("count2_2.org   : {}", tgt_a[1]);
    println!("count2_2 count : {}", tgt_a.iter().count());
    //copied.collect()
    let tgt_b = [1, 2, 3, 4, 5, 6, 7, 8, 9];
    let mut tgt_bvec : Vec<_> = tgt_b.iter().copied().collect();
    tgt_bvec[1]=999;
    println!("count3_1.copied: {}", tgt_bvec[1]);
    println!("count3_1 count : {}", tgt_bvec.iter().count());
    println!("count3_2.org   : {}", tgt_b[1]);
    println!("count3_2 count : {}", tgt_b.iter().count());
}
rangeオブジェクトにcount()2回目

2回目がエラーになるのは以下のようなコードです。

Error1_code

エラーの原因は消費されるからではなく、最初のcount()で所有権がmoveしてしまうからみたいです。それにしてもどこへ行ってしまうのだろう?

Error1_msg

rangeオブジェクトをcloneまたはby_ref

rangeオブジェクトをcloneしておけば、元のオブジェクトに何があろうと処理できるハズ。一方、イテレータには by_ref() という「借用」メソッドもあるので、借用も試みてます。なお借用はmutでないとダメだと。

ByREF_code

借用の結果、元のオブジェクトが move 済だといって怒られることは無くなったです。しかし借用先のcount()で「消費」されてしまうので戻った後再度countしても結果は0。エラーは出ないし、本来そうあるべきな1本筋の通った挙動ではあるのですが、今回の2回数えるという目的とは違うです。

一方元のオブジェクトをクローンしておきゃ、大丈夫だあ。まあ別なものを数えなおしているだけなので当たり前か。

ByREF_result

イテレータのclonedメソッドを使ってみる

いままでイテレータといいつつ range オブジェクトに作用させてきましたが、clonedメソッドに関しては「素の」数字の羅列であるrangeオブジェクトは扱えないようです。ホントか?

そこで配列を作り、その配列をiter()でイテレータ化し、cloned、さらにcollect()でVec型に直すということをしてしまいました。配列とその要素をクローンしたVec型が共存する感じですかね。mutで宣言したVec型の要素を一部書き換えても元の配列はびくともしません。当たり前か。

そしてどちらもiter().count()で要素数を数えられます。

cloned_code

これは2回目数えているというより、別々のものを1回づつ数えているってことだね。2回目じゃないじゃん。cloned_result

イテレータのcopiedメソッドを使ってみる

clonedメソッドに似たもので、copiedというメソッドもあるので実験してみました。copied_code

これでは違いがまったく分からんな。

copied_result

身に染みて違いを知るにはどうしたらよかでしょうか?また今度だな。

やっつけな日常(45) Rustに入ればRustに従え、イテレータで使えるメソッドその1 へ戻る

やっつけな日常(47) RustにいればRustに従え、イテレータで使えるメソッドその3 へ進む