简体   繁体   中英

How to convert code that uses the Read trait to use the Iterator trait instead?

I have some code which parses files, very similar to the following:

use std::io::Cursor;
use std::io::Result;
use std::io::Read;

fn main() {
   let v1 = [1, 2, 3, 4, 5];
   let mut c1 = Cursor::new(v1); // *

    println!("{:?}", read_one(&mut c1)); // 1
    println!("{:?}", read_three(&mut c1)); // 2, 3, 4     
    println!("{:?}", read_one(&mut c1)); // 5
    println!("{:?}", read_one(&mut c1)); // eof
}

fn read_one<R: Read>(r: &mut R) -> Result<u8> {
  let mut buf = [0; 1];
  r.read_exact(&mut buf)?;
  Ok(buf[0])
}

fn read_three<R: Read>(r: &mut R) -> Result<[u8; 3]> {
  let mut buf = [0; 3];
  r.read_exact(&mut buf)?;
  Ok(buf)
}

Here, read_one and read_three take a Read trait, get some bytes off of it, and return the result. I can then read the bytes piece by piece.

I'd like to convert this code to using an iterator instead. That is, have the * line be

let mut i1 = v1.iter();

Trying this, I ran into problems where I couldn't find functions to easily extract a number of items (analogous to read_exact ) or otherwise partially process an iterator. It seems most of the iterator functions are designed to consume the content entirely.

Is the iterator just a bad tool for this or am I using it wrong? If the latter, how do I convert this code to use iterators instead?

You can make your own trait for that

use std::io::{Error, ErrorKind, Result};

trait ReadExact {
    fn read_exact(&mut self, buf: &mut [u8]) -> Result<()>;
}

impl<T> ReadExact for T
where
    T: Iterator<Item = u8>,
{
    fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> {
        for p in buf {
            if let Some(b) = self.next() {
                *p = b;
            } else {
                return Err(Error::new(ErrorKind::UnexpectedEof, "Iterator exhausted"));
            }
        }
        Ok(())
    }
}

Playground

Here is the explanation of why you can't collect into an array

If you still want to use an iterator you can change return type to Vec and call r.take(n).collect();

This, however, will be a little bit slower.

You can write almost the same thing with iterators. The problem you'll run into is that arrays are difficult to work with, because they don't (as of December 2017) implement FromIterator . As user1541501 suggested , the simplest solution is to collect() into a Vec instead:

fn main() {
    let v1 = vec![1, 2, 3, 4, 5];
    let mut c1 = v1.into_iter();

    println!("{:?}", read_one(&mut c1)); // 1
    println!("{:?}", read_three(&mut c1)); // 2, 3, 4
    println!("{:?}", read_one(&mut c1)); // 5
    println!("{:?}", read_one(&mut c1)); // eof
}

fn read_one<I: Iterator<Item = u8>>(i: &mut I) -> Option<u8> {
    i.next()
}

fn read_three<I: Iterator<Item = u8>>(i: &mut I) -> Result<Vec<u8>, Vec<u8>> {
    let buf: Vec<u8> = i.take(3).collect();
    if buf.len() == 3 {
        Ok(buf)
    } else {
        Err(buf)
    }
}

Arrays also don't implement IntoIterator , so I turned v1 into a Vec<u8> to call v1.into_iter() . You could write v1.iter().cloned() instead.

The other significant change I made was to make read_one return Option instead of io::Result , and make read_three return Result<Vec<u8>, Vec<u8>> . That's because iterators can only fail by running out of data. (If read_three fails, the Err variant contains the collected elements.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM