RustArena

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:

  1. String::new() makes an empty, growable buffer. It must be mut because read_line writes into it.
  2. read_line(&mut line) appends the next line to the buffer, including the newline \n. It returns a Result holding the number of bytes read (0 means end of input), so .expect(...) unwraps it or stops with that message.
  3. .trim() removes the newline and surrounding spaces. Without it, "41\n".parse() fails.
  4. .parse() converts text into a number. It needs to know the target type, given here by let age: u32 (or write .parse::<u32>()). It returns a Result, 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

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

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)

Practice it