Skip to content
Select theme

How to parse an integer from user input

Short answer: read the line with io::stdin().read_line(&mut input), trim() the newline off, parse() it, and match on the result. Every step can fail — the input can be closed before anything arrives, the text can be garbage — and the one thing that turns those failures into a crash is unwrap().

From a fresh project — cargo new stdin-demo, then replace src/main.rs with each block below. These programs read standard input, so the runs below were captured with the input piped in: printf '42\n' | cargo run is the equivalent of typing 42 and pressing Enter.

Terminal window
printf '42\n' | cargo run -q
read "42\n"

The \n at the end is the Enter key: read_line hands back everything typed up to and including it. It appends to the String you pass — that is why it takes &mut — so a fresh String::new() per read, or a clear() between reads, or the lines pile up inside one buffer.

read_line also returns io::Result<usize>, the number of bytes read. That number gets a job of its own further down.

"42\n" is not a number as far as parse is concerned, and neither is anything else a human actually types:

"42\n" -> invalid digit found in string
" 42 " -> invalid digit found in string
"42\r\n" -> invalid digit found in string

Trailing newline, leading space, the \r a Windows terminal sends with every Enter — all the same error. What each kind of ParseIntError means is How to convert a String to a number’s territory; here the fix is one method:

"42\n" -> 42
" 42 " -> 42
"42\r\n" -> 42

trim() strips whitespace from both ends — spaces, tabs, \n, \r — and changes nothing else. Everything left still has to be a valid i32: "12 apples".trim().parse::<i32>() fails the same way.

For a program that prompts, the shape is a loop: ask, read, trim, parse, and repeat on failure.

Terminal window
printf '42\n' | cargo run -q
Type a number: 42 it is
Terminal window
printf 'abc\n12\n' | cargo run -q
Type a number: that is not an integer, try again
Type a number: 12 it is
Terminal window
printf '' | cargo run -q
Type a number: input closed, giving up

Three details carry the whole program.

print! instead of println! keeps the answer on the prompt’s line, and the flush() matters because stdout is line-buffered: without it the prompt sits in a buffer while the program blocks on a read that the user has seen no reason to answer.

input.clear() is the append semantics from the first section at work: read_line adds to whatever is there, so a reused String without a clear would parse "abc\n12\n" on the second attempt — and keep failing no matter how correct the next answer was.

The bytes == 0 check is the EOF case. When stdin closes — a pipe that ran out, or Ctrl-D on an empty line at a terminal — read_line returns Ok(0) and leaves the string empty. Without the check the loop would print prompts forever: the read keeps succeeding, the parse keeps failing, nothing ends it. With it, a closed input is a clean exit instead of a hang.

The same parse written the way most first drafts write it:

thread 'main' (8919005) panicked at src/main.rs:3:39:
called `Result::unwrap()` on an `Err` value: ParseIntError { kind: InvalidDigit }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Exit code 101. The number in parentheses is the thread id, and it is different on every run. unwrap() took “the user typed something wrong” — an ordinary, expected outcome of asking a human for input — and turned it into a crash. The match version keeps the program in control of the same failure.

  • How to convert a String to a number — the full tour of the errors parse can return.
  • What happens when the number the user types does not fit — a panic in debug builds, a silent wrap in release — is the next article’s subject.
  • Working with numbers — the rest of the chapter.