Step 4 of 7
Reading every line until the end
Often you don't know how many lines are coming: a log file, a list of orders, a CSV export. Then you read until the input ends. When the input comes from a file, that's the end of the file (often called EOF). When you type into a terminal, you signal the end with Ctrl+D (Linux and macOS) or Ctrl+Z then Enter (Windows).
use std::io;
fn main() {
let mut count = 0;
for line in io::stdin().lines() {
let line = line.expect("failed to read a line");
count += 1;
println!("{count}: {line}");
}
println!("{count} lines");
}
apples
pears
plums
1: apples
2: pears
3: plums
3 lines
io::stdin().lines()hands out the input one line at a time, asStrings without the newline.- Each line comes wrapped in a
Result, because reading can fail.let line = line.expect(...)unwraps it (shadowing again). - The loop ends by itself when the input runs out.
The classic form: lock() and BufRead
In existing code and older answers online you'll see this form. It does the same thing:
use std::io::{self, BufRead};
fn main() {
let mut total = 0;
for line in io::stdin().lock().lines() {
let line = line.expect("failed to read a line");
let n: i32 = line.trim().parse().expect("not a number");
total += n;
}
println!("total: {total}");
}
10
20
12
total: 42
use std::io::{self, BufRead};importsioitself (self) andBufRead, a trait: a named set of methods that several types share (module 22).lines()on a locked stdin comes fromBufRead, and a trait's methods can only be called when the trait is imported. Without the import, you get error E0599:no method named `lines` found for struct `StdinLock<'a>`, with a help line suggestinguse std::io::BufRead;..lock()takes hold of stdin once for the whole loop instead of on every read, which is faster for big inputs.io::stdin().lines(), added in Rust 1.62, does that for you.
Skipping blank lines
Real input often has empty lines (a trailing blank line at the end of a file is very common). Skip them with continue:
use std::io;
fn main() {
let mut names = 0;
for line in io::stdin().lines() {
let line = line.expect("failed to read a line");
if line.trim().is_empty() {
continue;
}
names += 1;
println!("{names}. {}", line.trim());
}
}
Ana
Ben
1. Ana
2. Ben
With read_line
read_line also tells you when the input has ended: it returns the number of bytes it read, and 0 means there was nothing left.
use std::io;
fn main() {
let mut line = String::new();
let mut longest = 0;
loop {
line.clear();
let bytes = io::stdin().read_line(&mut line).expect("failed to read a line");
if bytes == 0 {
break;
}
let length = line.trim().chars().count();
if length > longest {
longest = length;
}
}
println!("longest line: {longest} characters");
}
short
a bit longer
mid
longest line: 12 characters
Common mistakes
- Using the line before unwrapping it.
line.trim()straight on the loop variable gives E0599,no method named `trim` found for enum `Result<T, E>`: unwrap it first with.expect(...). - Forgetting the
BufReadimport withlock().lines()(see above). - The program "hangs" in a terminal. It's waiting for more input: the loop only ends at the end of input, so press Ctrl+D (or Ctrl+Z, Enter on Windows).
Your task
Whole numbers (possibly negative) arrive one per line, as many as there are. Skip blank lines (empty or only spaces). Print how many numbers there were and their total:
count: 4, total: 46
Challenge 2
Read log lines until the end of the input. Count all lines, and the lines that contain ERROR (in capitals, exactly). Print:
2 errors in 5 lines
Challenge 3
Read a list of names, one per line, until the end of input. Skip blank lines, trim each name, make it uppercase, and number the names from 1:
1. ANA
2. BEN
3. CY
Previous: Reading several lines Next: Several values on one line: split_whitespace