Rustで文字列・ファイル、両方からbyteを読みたいだけなのに

バイト配列をイテレータで読み込みたい

Rustでプログラミング言語を作っている。基本的には、ソースコードはファイルを読み込むのだが、テストのために固定の文字列をソースコードとして読ませたくなることが多い。そういうわけで、字句解析器(Lexer)に両方を受け付けられるインターフェースを作ろうとしたのだが、これが意外と面倒なことに気づいたので書く。

文字列もファイルからのストリームもイテレータなのに

&strからイテレータを取り出すときは、bytesメソッドを使えばIteratorが取れる。一方、ファイルではstd::io::BufReaderにもbytesメソッドがある。これらはReadトレイトのメソッドなので、「じゃあ、Read型を引数で受け取ればいいのかな」と安易な気持ちで書いてみたら、全然ビルドが通らない。よーく見ると、当然だった。

まず、文字列&strの場合。

    let s = "Please iterate me!";
    let mut bytes = s.bytes();
    while let Some(b) = bytes.next() {
        print!({:?}, b);
    }

そして、ファイルから読み込む場合。

    let f = File::open("_.txt").unwrap();
    let mut bytes = BufReader::new(f).bytes();
    while let Some(Ok(b)) = bytes.next() {
        print!({:?}, b);
    }

おわかりいただけただろうか……nextメソッドの返す型が違うのである。str::bytes()Iterator<Item=u8>を返し、BufReader::bytes()Iterator<Item=::std::io::Result<u8>>を返す。ファイルは間にstd::io::Resultが挟まっているのである。だから当然これらを共通の引数にしてメソッドを定義することはできない。

しょうがない

というわけで、普通にどちらでもいけるようにがりがりifで書き直しました。そのまんま、何の工夫もしていないので見せるほどのコードでもない。しかし、もうちょっと何とかならんかなあ。

補足

Iterator<Item=::std::io::Result<u8>>は、Iterator<Item=Result<u8>>とは書けない。std::result::Resultだと思われてしまうので。ただ、Iterator<Item=std::io::Result<u8>>とも書けない。うまくパースできないようなので、Iterator<Item=::std::io::Result<u8>>と書く必要があります。これって::を省いて書けるようになったりするんかな。まあともかく、そんな感じです。