LoginSignup
2
2

More than 3 years have passed since last update.

Rust勉強中 - その21 -> イテレータ

Last updated at Posted at 2019-10-29

自己紹介

出田 守と申します。
しがないPythonプログラマです。
情報セキュリティに興味があり現在勉強中です。CTFやバグバウンティなどで腕を磨いています。主に低レイヤの技術が好きで、そっちばかり目が行きがちです。

Rustを勉強していくうえで、読んで学び、手を動かし、記録し、楽しく学んでいけたらと思います。

環境

新しい言語を学ぶということで、普段使わないWindowsとVimという新しい開発環境で行っています。
OS: Windows10 Home 64bit 1903
CPU: Intel Core i5-3470 @ 3.20GHz
Rust: 1.38.0
RAM: 8.00GB
Editor: Vim 8.1.1
Terminal: PowerShell

前回

前回はクロージャについて学びました。
Rust勉強中 - その20

イテレータ

イテレータは、Iteratorトレイトを実装し、ある値の列を生成する値です。

std::iter::Iteratorとstd::iter::IntoIterator

イテレータはIteratorトレイトを実装しています。
また、IntoIteratorトレイトは、ある型がイテレータを生成することができるならば実装でき、その型をイテレート可能(iterable)と言うそうです。
イテレータが生成する値のことをアイテムと言います。

trait Iterator {
    type Item;

    fn next(&mut self) -> Option<Self::Item>;
    ...
}
trait IntoIterator {
    type Item;
    type IntoIter: Iterator<Item=Self::Item>;

    fn into_iter(self) -> Self::IntoIter;
}

Iteratorは、nextメソッド以外は非常に便利なデフォルトメソッドがズラーっと並びます。nextメソッドはアイテムが存在する限りSome(Item)を返し、空になればNoneを返します。
IntoIteratorのinto_iterメソッドは、その型が持つイテレータ型を返します。

以下に例を示します。

fn main() {
    let v = vec![0, 1, 2];
    let mut iter = v.into_iter();
    println!("{:?}", iter.next()); // 0
    println!("{:?}", iter.next()); // 1
    println!("{:?}", iter.next()); // 2
    println!("{:?}", iter.next()); // None
    println!("{:?}", iter.next()); // None
    println!("{:?}", iter.next()); // None
}

nextでNoneを返した後、再びnextを呼び出した場合の動作はたいていNoneを返してくれるそうですが、実装した型によりけりだそうです。もし、Noneを返し続けてほしい場合はfuseメソッドを使います。VecはNoneを返してくれるので結果は同じですが・・・

fn main() {
    let v = vec![0, 1, 2];
    let mut iter = v.into_iter().fuse();
    println!("{:?}", iter.next()); // 0
    println!("{:?}", iter.next()); // 1
    println!("{:?}", iter.next()); // 2
    println!("{:?}", iter.next()); // None
    println!("{:?}", iter.next()); // None
    println!("{:?}", iter.next()); // None
}

ある型の共有参照でイテレータを作成した場合、イテレータが生成するアイテムは、そのアイテムの共有参照を返します。可変参照も同様です。そのまま所有権を渡した場合は、アイテムの所有権を返します。

値渡し
fn main() {
    ...
    for item in v {
        println!("{}", get_type(item)); // i32
    }
}
共有参照
fn main() {
    ...
    for item in &v {
        println!("{}", get_type(item)); // &i32
    }
}
可変参照
fn main() {
    ...
    for item in &mut v {
        println!("{}", get_type(item));
    }
}

このようにできるのは各型に対する複数のIntoIteratorを実装しているためです。

また、多くのコレクション型にはiterメソッドとiter_mutメソッドが定義されており、それぞれ、共有参照と可変参照のアイテムを取得するイテレータを返します。

イテレータアダプタ(iterator adaptors)とコンシューマ(consumers)

イテレータを作成しただけでは何もしません。イテレータを加工して新たなイテレータを作成するか、先ほどのnextやfor文などで消費するかをします。
イテレータを入力とし、新たなイテレータを作成するメソッドなどをイテレータアダプタと言います。イテレータを消費してアイテムを返すメソッドなどをコンシューマと言います。

これらのメソッドについては、必要に応じてイテレータの標準ドキュメントをみるのが良いと思っています。今、ズラーっと列挙しても覚えられませんので。(本当は早く先へ進みたいだけです。すみません)
以下の記事も非常に参考になりました。
Qiita - Rustのイテレータの網羅的かつ大雑把な紹介

Iteratorトレイトを実装してみる

以下は学習のためにIteratorトレイトを実装してみました。

struct Alphabet {
    start: char,
    end:   char
}
impl Iterator for Alphabet {
    type Item = char;

    fn next(&mut self) -> Option<Self::Item> {
        if self.start>=self.end || self.start<'a' || self.start>'z' {
            None
        } else {
            let c = self.start;
            self.start = ((self.start as u8)+1) as char; // update next char
            Some(c)
        }
    }
}

fn main() {
    ...

    let alpha = Alphabet {start: 's', end: '{'};
    let mut iter = alpha.into_iter();
    for e in iter {
        println!("{}", get_type(e));
    }
}
s
t
u
v
w
x
y
z

IntoIteratorがあらゆる型に対して包括的に実装されているため、Iteratorを実装することで、このような簡単な場合においてイテレータを作成できます。もっと複雑なイテレータを作成したい場合はIteratorとIntoIteratorを良い感じに実装する必要があります。
なお、上記の例のようなイテレータを作成する際はRangeを使えば、簡単かつもっと広い範囲の値も扱えます。

ソース

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

struct Alphabet {
    start: char,
    end:   char
}
impl Iterator for Alphabet {
    type Item = char;

    fn next(&mut self) -> Option<Self::Item> {
        if self.start>=self.end || self.start<'a' || self.start>'z' {
            None
        } else {
            let c = self.start;
            self.start = ((self.start as u8)+1) as char; // update next char
            Some(c)
        }
    }
}

fn main() {
    let mut v = vec![0, 1, 2];
    let mut iter = (&v).into_iter().fuse();
    println!("{:?}", iter.next()); // 0
    println!("{:?}", iter.next()); // 1
    println!("{:?}", iter.next()); // 2
    println!("{:?}", iter.next()); // None
    println!("{:?}", iter.next()); // None
    println!("{:?}", iter.next()); // None
    for item in &mut v {
        println!("{}", get_type(item));
    }

    let alpha = Alphabet {start: 's', end: '{'};
    let mut iter = alpha.into_iter();
    for e in iter {
        println!("{}", e);
    }
}
Some(0)
Some(1)
Some(2)
None
None
None
&mut i32
&mut i32
&mut i32
s
t
u
v
w
x
y
z

今回はここまでー。
ちょっと端折りすぎた感がありますが、必要に応じてドキュメントや上で紹介させていただいた記事を参照するということで・・・

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2