Reading user input in Rust (stdin, read_line and parse)
How to read a line, a number, or all of standard input in Rust with read_line, trim, parse and lines(), and how to handle bad input.
Programs read input from standard input (stdin): the keyboard, or a file or another program piped in. In Rust you reach it through std::io::stdin().
Read one line, then parse it
use std::io;
fn main() {
let mut line = String::new();
io::stdin().read_line(&mut line).expect("failed to read input");
let age: u32 = line.trim().parse().expect("age must be a whole number");
println!("Next year you'll be {}", age + 1);
}
41
Next year you'll be 42
Step by step:
String::new()makes an empty, growable buffer. It must bemutbecauseread_linewrites into it.read_line(&mut line)appends the next line to the buffer, including the newline\n. It returns aResultholding the number of bytes read (0 means end of input), so.expect(...)unwraps it or stops with that message..trim()removes the newline and surrounding spaces. Without it,"41\n".parse()fails..parse()converts text into a number. It needs to know the target type, given here bylet age: u32(or write.parse::<u32>()). It returns aResult, because the text might not be a number.
Handling bad input without crashing
expect stops the program. To recover, match on the Result:
match line.trim().parse::<i32>() {
Ok(n) => println!("got {n}"),
Err(e) => println!("not a number: {e}"),
}
The error prints as invalid digit found in string, or cannot parse integer from empty string for an empty line.
Reading every line
For input of unknown length, loop over lines() on a locked stdin (locking once is faster than locking per read). lines() strips the newline for you and yields a Result per line:
use std::io::{self, BufRead};
for line in io::stdin().lock().lines() {
let line = line.expect("read error");
// use line
}
To grab everything at once, use io::read_to_string(io::stdin()), then split it with .lines() or .split_whitespace(). split_whitespace is the easy way to read several numbers on one line.
Common mistakes
- Forgetting
trim(). The parse fails on the trailing newline withinvalid digit found in string. - Forgetting
muton the buffer:
fn main() {
let line = String::new();
std::io::stdin().read_line(&mut line).unwrap();
}
error[E0596]: cannot borrow `line` as mutable, as it is not declared as mutable
- Reusing one buffer in a loop without clearing it.
read_lineappends, so the second line is added to the first. Callline.clear()before each read, or uselines(). - Prompts that don't appear.
print!("Name: ")without a newline may stay in the output buffer. Callio::stdout().flush()(withuse std::io::Write;) before reading.
Example
use std::io::{self, BufRead};
fn main() {
let stdin = io::stdin();
let mut name = String::new();
stdin.read_line(&mut name).expect("failed to read the name");
let name = name.trim();
let mut total = 0;
let mut skipped = 0;
for line in stdin.lock().lines() {
let line = line.expect("failed to read a line");
for word in line.split_whitespace() {
match word.parse::<i32>() {
Ok(n) => total += n,
Err(e) => {
println!("skipping {word:?}: {e}");
skipped += 1;
}
}
}
}
println!("{name} scored {total} ({skipped} bad values)");
}
Output:
skipping "x": invalid digit found in string
Ferris scored 39 (1 bad values)