Skip to content
Select theme

How to read standard input until the end

Short answer: Lock stdin once and loop over lines() — the iterator ends by itself when the input does.

From a fresh project — cargo new read-all — replace src/main.rs with each block below and run it with cargo run. The transcripts pipe the input in (printf 'ada\ngrace\nlinen\n' | cargo run -q), so the bytes shown are the bytes the command produces; typing the lines by hand and finishing with Ctrl-D ends with the same output.

$ printf 'ada\ngrace\nlinen\n' | cargo run -q
ada
grace
linen

That is cat in Rust. lines() is a method of the BufRead trait, and stdin.lock() is the StdinLock that implements it — the lock is taken once, outside the loop, instead of per line. The iterator yields None at EOF, so the for loop ends on its own: nothing counts bytes, nothing watches for a sentinel. Unlike read_line, lines() strips the trailing newline, which is why this loop prints with println! and not print!.

A final line with no newline still arrives. Same program, input whose last line ends at the byte rather than at \n:

$ printf 'ada\ngrace' | cargo run -q
ada
grace

The transcripts look identical, but the second one’s output does not end in a newline — the input’s last line didn’t have one to keep, and println! only adds one after the text it is given.

$ printf 'one\ntwo\nthree\n' | cargo run -q
3 lines, 0 unreadable
$ printf 'one\ntwo\n\xff\xfe\nthree\n' | cargo run -q
skipped a line: stream did not contain valid UTF-8
3 lines, 1 unreadable

lines() hands back a Result per line, and a line holding bytes that are not UTF-8 is an Err — io::ErrorKind::InvalidData, message as shown. read_line has the same limitation, because it also reads into a String. Empty input simply never enters the loop body: 0 lines, 0 unreadable.

$ printf 'ada\ngrace\n' | cargo run -q
ada
grace
$ printf 'ada\ngrace' | cargo run -q
ada
grace

Here the newline is still in buf, so the loop echoes with print! and the output reproduces the input byte for byte — including the missing final newline in the second run. Ok(0) is the exit: it means the input ended, not that a line was empty, which is the same signal How to read a line from standard input covers for a single line. read_line is an inherent method on Stdin that takes the lock itself, so this version needs no BufRead import and no explicit lock().

$ printf 'ok\n\xff\xfe\nend\n' | cargo run -q
ok
��
end

read_until is a BufRead method, so it needs the lock — calling it on io::stdin() itself fails with E0599, no method named read_until found for struct Stdin. It appends raw bytes to a Vec<u8> up to and including the delimiter, which is the only version of this loop that survives a stream that isn’t text; from_utf8_lossy swaps each invalid sequence for U+FFFD, the � above.

Exiting with a status code is the next step in this chapter.