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.
Read the line
Section titled “Read the line”use std::io;
fn main() { let mut input = String::new(); io::stdin().read_line(&mut input).unwrap(); println!("read {input:?}");}printf '42\n' | cargo run -qread "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.
The newline breaks parse
Section titled “The newline breaks parse”"42\n" is not a number as far as parse is concerned, and neither is anything else a human actually types:
fn main() { for typed in ["42\n", " 42 ", "42\r\n"] { match typed.parse::<i32>() { Ok(n) => println!("{typed:?} -> {n}"), Err(e) => println!("{typed:?} -> {e}"), } }}"42\n" -> invalid digit found in string" 42 " -> invalid digit found in string"42\r\n" -> invalid digit found in stringTrailing 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:
fn main() { for typed in ["42\n", " 42 ", "42\r\n"] { match typed.trim().parse::<i32>() { Ok(n) => println!("{typed:?} -> {n}"), Err(e) => println!("{typed:?} -> {e}"), } }}"42\n" -> 42" 42 " -> 42"42\r\n" -> 42trim() 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.
Ask until the answer is valid
Section titled “Ask until the answer is valid”For a program that prompts, the shape is a loop: ask, read, trim, parse, and repeat on failure.
use std::io::{self, Write};
fn main() { let mut input = String::new(); loop { print!("Type a number: "); io::stdout().flush().unwrap();
input.clear(); let bytes = io::stdin().read_line(&mut input).unwrap(); if bytes == 0 { println!("input closed, giving up"); return; }
match input.trim().parse::<i32>() { Ok(n) => { println!("{n} it is"); break; } Err(_) => println!("that is not an integer, try again"), } }}printf '42\n' | cargo run -qType a number: 42 it isprintf 'abc\n12\n' | cargo run -qType a number: that is not an integer, try againType a number: 12 it isprintf '' | cargo run -qType a number: input closed, giving upThree 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 version that panics
Section titled “The version that panics”The same parse written the way most first drafts write it:
fn main() { let input = String::from("abc\n"); let n: i32 = input.trim().parse().unwrap(); println!("{n}");}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 backtraceExit 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
parsecan 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.