What's the idiomatic way to create a iterator that owns some intermediate data and also points to it?

主宰稳场 提交于 2020-05-15 09:32:05

问题


I'm trying to create a struct that wraps around stdin to provide something like C++'s std::cin.

I want to keep a String with the current line of the input and a SplitAsciiWhitespace iterator to its current token. When I reach the end of the iterator, I want to get a new line.

I'm not worried about error checking and I'm not interested in any crates. This is not for production code, it's just for practicing. I want to avoid using unsafe, as a way to practice the correct mindset.

The idea is that I can use it as follows:

let mut reader = Reader::new();
let x: i32 = reader.read();
let s: f32 = reader.read();

My current attempt is the following, but it doesn't compile. Can somebody give me a pointer on the proper way to do this?

struct Reader<'a> {
    line: String,
    token: std::str::SplitAsciiWhitespace<'a>,
}

impl<'a> Reader<'a> {
    fn new() -> Self {
        let line = String::new();
        let token = line.split_ascii_whitespace();
        Reader { line, token }
    }

    fn read<T: std::str::FromStr + std::default::Default>(&'a mut self) -> T {
        let token = loop {
            if let Some(token) = self.token.next() {
                break token;
            }
            let stdin = io::stdin();
            stdin.read_line(&mut self.line).unwrap();
            self.token = self.line.split_ascii_whitespace();
        };
        token.parse().unwrap_or_default()
    }
}

This question explains why it can't be done this way but does not provide an alternative solution. The "How do I fix it" section simply says "don't put these two things in the same struct", but I can't think of a way to do it separately while keeping a similar interface to the user.


回答1:


Found a solution: keeping track of how much of the string we've read so far by using a simple index.

It does require some pointer arithmetic, but seems to work nicely.

Not sure if this counts as "idiomatic" Rust, tho.

struct Reader {
    line: String,
    offset: usize,
}

impl Reader {
    fn new() -> Self {
        Reader { line: String::new(), offset: 0 }
    }
    fn next<T: std::str::FromStr + std::default::Default> (&mut self) -> T {
        loop {
            let rem = &self.line[self.offset..];
            let token = rem.split_whitespace().next();
            if let Some(token) = token {
                self.offset = token.as_ptr() as usize - self.line.as_ptr() as usize + token.len();
                return token.parse::<T>().unwrap_or_default();
            }
            self.line.clear();
            std::io::stdin().read_line(&mut self.line).unwrap();
            self.offset = 0;
        }
    }
}


来源:https://stackoverflow.com/questions/61308010/whats-the-idiomatic-way-to-create-a-iterator-that-owns-some-intermediate-data-a

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!