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.
The loop
Section titled “The loop”use std::io::{self, BufRead};
fn main() { let stdin = io::stdin(); for line in stdin.lock().lines() { match line { Ok(text) => println!("{text}"), Err(e) => eprintln!("error: {e}"), } }}$ printf 'ada\ngrace\nlinen\n' | cargo run -qadagracelinenThat 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 -qadagraceThe 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.
When a line is not valid UTF-8
Section titled “When a line is not valid UTF-8”use std::io::{self, BufRead};
fn main() { let stdin = io::stdin(); let mut lines = 0; let mut bad = 0; for line in stdin.lock().lines() { match line { Ok(_) => lines += 1, Err(e) => { bad += 1; eprintln!("skipped a line: {e}"); } } } println!("{lines} lines, {bad} unreadable");}$ printf 'one\ntwo\nthree\n' | cargo run -q3 lines, 0 unreadable$ printf 'one\ntwo\n\xff\xfe\nthree\n' | cargo run -qskipped a line: stream did not contain valid UTF-83 lines, 1 unreadablelines() 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.
If you want the newline back
Section titled “If you want the newline back”use std::io;
fn main() { let stdin = io::stdin(); let mut buf = String::new(); loop { buf.clear(); match stdin.read_line(&mut buf) { Ok(0) => break, Ok(_) => print!("{buf}"), Err(e) => { eprintln!("error: {e}"); break; } } }}$ printf 'ada\ngrace\n' | cargo run -qadagrace$ printf 'ada\ngrace' | cargo run -qadagraceHere 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().
Input that is not text at all
Section titled “Input that is not text at all”use std::io::{self, BufRead};
fn main() { let mut stdin = io::stdin().lock(); let mut buf = Vec::new(); loop { buf.clear(); match stdin.read_until(b'\n', &mut buf) { Ok(0) => break, Ok(_) => println!("{}", String::from_utf8_lossy(&buf).trim_end()), Err(e) => { eprintln!("error: {e}"); break; } } }}$ printf 'ok\n\xff\xfe\nend\n' | cargo run -qok��endread_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.
- How to read a line from standard input — the single line this loop repeats, and the
Ok(0)that ends it - How to read a file line by line — the same
lines()loop on a file - How to read several values from one line of input — what to do with a line once you have it
- Back to Building a command-line tool
Exiting with a status code is the next step in this chapter.