How to read a line from standard input
Short answer: io::stdin().read_line(&mut line) reads the line including its trailing newline, so "Ada" comes back as "Ada\n" — four bytes, not three. line.trim() gives you the text without it.
From a fresh project — cargo new read-line — replace src/main.rs with each block below and run it with cargo run. The project name only shows up in cargo’s own output. The transcripts pipe the input in (printf 'Ada\n' | cargo run -q) so the bytes shown are the bytes the command produces; typing the line by hand ends with the same output.
What read_line leaves in the string
Section titled “What read_line leaves in the string”use std::io;
fn main() { let mut line = String::new(); io::stdin().read_line(&mut line).unwrap();
println!("bytes: {}", line.len()); println!("debug: {line:?}"); println!("hello, {line}"); println!("done");}$ printf 'Ada\n' | cargo run -qbytes: 4debug: "Ada\n"hello, Ada
doneThree facts in one run. read_line reads up to and including the newline, which is why "Ada" is four bytes and why {:?} shows the \n that {} hides. len() counts bytes, not characters. And because the string ends with its own newline, println!("hello, {line}") adds a second one: the blank line before done. Nothing is broken; the newline is simply still in there.
Why the parse fails
Section titled “Why the parse fails”use std::io;
fn main() { let mut line = String::new(); io::stdin().read_line(&mut line).unwrap();
let age: u32 = line.parse().unwrap(); println!("next year you will be {}", age + 1);}$ printf '42\n' | cargo run -q
thread 'main' (4754163) panicked at src/main.rs:7:33:called `Result::unwrap()` on an `Err` value: ParseIntError { kind: InvalidDigit }note: run with `RUST_BACKTRACE=1` environment variable to display a backtraceThe newline is not a digit, so parse rejects the whole string and unwrap() turns that Err into the panic above; the blank first line is part of the output. The thread id changes every run, the message doesn’t. With no input at all the kind is Empty instead of InvalidDigit — same panic, same cause.
The fix
Section titled “The fix”use std::io;
fn main() { let mut line = String::new(); io::stdin().read_line(&mut line).unwrap();
match line.trim().parse::<u32>() { Ok(age) => println!("next year you will be {}", age + 1), Err(e) => println!("not a number: {e}"), }}$ printf '42\n' | cargo run -qnext year you will be 43trim returns a &str slice of the same string, nothing allocated, with the whitespace off both ends, so the newline goes and the digits stay. It also clears a Windows \r\n, because \r is whitespace too; trim_end_matches('\n') would leave the \r behind. How to trim whitespace and strip characters has the rest of the family, including the to_string() for when the trimmed text has to outlive the string it came from.
The pattern to copy
Section titled “The pattern to copy”use std::io::{self, Write};
fn main() { print!("name: "); io::stdout().flush().unwrap();
let mut line = String::new(); io::stdin().read_line(&mut line).unwrap();
let name = line.trim(); println!("hello, {name}");}$ printf 'Ada\n' | cargo run -qname: hello, AdaThe prompt needs its own flush(). Stdout is line-buffered, so print! without a newline sits in the buffer until the first println! or the end of the program; run it interactively and you type before the question appears. How to print without a trailing newline covers why. And read_line appends rather than replaces, so a program that asks two questions needs line.clear() between them, or the second answer lands after the first.
When there is no line
Section titled “When there is no line”use std::io;
fn main() { let mut line = String::new(); let bytes = io::stdin().read_line(&mut line).unwrap();
println!("returned: {bytes}"); println!("string: {line:?}"); println!("trimmed: {:?}", line.trim());}$ printf 'Ada\n' | cargo run -qreturned: 4string: "Ada\n"trimmed: "Ada"$ cargo run -q < /dev/nullreturned: 0string: ""trimmed: ""$ printf '\n' | cargo run -qreturned: 1string: "\n"trimmed: ""The return value is the byte count, and it is the only thing that tells an empty line from no line: Ok(0) means the input ended (Ctrl-D at a terminal, or a pipe that ran dry) and the string is left as it was. A line that is nothing but a newline is Ok(1). Both trim to "", so when an empty answer needs different handling from a missing one, branch on the count, not the string.
- How to trim whitespace and strip characters — the trim family, and the
\ra Windows line ending leaves - How to print without a trailing newline — why the prompt needs its own flush
- How to read a file line by line — the same
read_lineon a file, andlines(), which strips the newline for you - Back to Building a command-line tool
Reading several values from one line of input is the next step in this chapter.