2

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
    The main answer to the linked question *does* have a "How do I fix it?" section. This question should explain *why* this section does not apply here. – mcarton Apr 20 '20 at 15:24
  • 1
    A sketch of the kind of thing you might do to solve it: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f1a7871a6b744035f6762c79f459cc4f – Peter Hall Apr 20 '20 at 16:39
  • The linked question is a bit too general in its proposed solutions. I was just thinking on how it applied in my case, or if there was a better way to approach this problem in the first place. – Pedro Ciambra Apr 20 '20 at 20:08
  • @PeterHall, I eventually arrived at a similar solution. I was just wondering if there was a way to do it without using `unsafe`. I'll add that to the question. – Pedro Ciambra Apr 20 '20 at 20:17

1 Answers1

0

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;
        }
    }
}