Both Python and Rust offer convenience API for iterating over the lines in a file, they just chose different tradeoffs of convenience vs completeness. Rust has chosen extra convenience and strips the newlines at the expense of being able to distinguish whether the final line ends with a terminator. Python has chosen the opposite, at the expense of all line parsers having to take the final \n into account, and also take into account that it's optional.
But BufRead::lines() is just a convenience AP; if it doesn't meet your needs, you can always drop to the lower-level read_line() method:
let mut line = String::new();
while input.read_line(&mut line)? != 0 {
let line = std::mem::take(&mut line);
// ...
}
If you use this kind of code in multiple places, or just want the convenience of a for loop, you can abstract it into a utility function that returns an iterator, such as:
fn full_lines(mut input: impl BufRead) -> impl Iterator<Item = io::Result<String>> {
std::iter::from_fn(move || {
let mut vec = String::new();
match input.read_line(&mut vec) {
Ok(0) => None,
Ok(_) => Some(Ok(vec)),
Err(e) => Some(Err(e)),
}
})
}
Then you can use a for loop similar to the one in Python:
for line in full_lines(io::stdin().lock()) {
let line = line?;
// ...
}
With additional effort it is even possible to make full_lines a method on anything that implements BufRead:
trait FullLines: BufRead + Sized {
fn full_lines<'a>(self) -> Box<dyn Iterator<Item = io::Result<String>> + 'a>
where
Self: 'a,
{
Box::new(full_lines(self))
}
}
// Provide a blanket implementation of FullLines for any T
// that implements BufRead
impl<T: BufRead> FullLines for T {}
// Usage:
use FullLines;
for line in io::stdin().lock().full_lines() {
let line = line?;
// ...
}