问题
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