Rust (2024 edition) Module 2 – Values, types, functions and control flow
Standard input: read lines, parse numbers safely
Read lines from standard input in Rust, notice the end of input, parse text into numbers with str::parse, and answer bad input with a message.
What you will learn
- Read lines from standard input with read_line and lines, and detect the end of input
- Parse text into numbers with str::parse, and handle the Result it returns
- Predict which strings parse as i32, u32 and f64, and trim input before parsing it
- Write an interactive program that asks again after invalid input instead of crashing
- Validate an amount of money typed as text and convert it into whole paise
Before you start
What you’ll learn · Before you start
What you will learn
- Read lines from standard input with read_line and lines, and detect the end of input
- Parse text into numbers with str::parse, and handle the Result it returns
- Predict which strings parse as i32, u32 and f64, and trim input before parsing it
- Write an interactive program that asks again after invalid input instead of crashing
- Validate an amount of money typed as text and convert it into whole paise
Before you start
On this page
Type 42 at a prompt on Linux or macOS and press Enter, and read_line hands your program "42\n": three bytes, the
line break included. "42\n".parse::<u32>() is an error, and so are " 42", "1,000" and an empty line, so a
program that trusts its input stops on the first one.
This lesson reads lines from standard input, notices when the input ends, and parses text into numbers the safe way:
trim the line, parse it, and turn every Err into a message instead of a crash. Each example reads a recorded input,
shown above its output, and ran on Rust 1.99.0.
Each read appends a line to your String, line break and all
A program’s standard input: The stream a program reads its input from: the keyboard when it runs in a terminal, or a file or another program's output when the shell redirects it, as in ./app < answers.txt. Rust reads it through std::io::stdin(). is the keyboard in a terminal, or a file when the shell
redirects it. io::stdin().read_line(&mut line) reads one line from it and adds the line to the end of line, a
String that the program owns. It returns how many bytes it read:
// read_line adds one line to the String it is given, line break included, and says how many bytes it read.
use std::io;
fn main() {
let mut line = String::new();
loop {
let bytes = io::stdin().read_line(&mut line).expect("standard input can be read");
if bytes == 0 {
println!("0 bytes: the input has ended");
break;
}
println!("{bytes} bytes read, and line holds {line:?}");
}
} Input (standard input)
42 7
Output
3 bytes read, and line holds "42\n" 4 bytes read, and line holds "42\n 7 \n" 0 bytes: the input has ended
Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O read_line.rs && ./read_line
Three facts are in that output. The line break stays: the first read gave 3 bytes, 4, 2 and \n. The second read
added to what was there, so line held both lines, "42\n 7 \n"; the documentation says the previous content is
kept, and the fix is line.clear() before each read. And at the end of input: The point where standard input has nothing more to give, because a file or a pipe has ended or the terminal's input was closed. read_line then returns Ok(0), and lines() returns None. the
call returned 0 bytes, which is the one sure sign that nothing more will come: an empty line still has its \n, 1
byte.
expect("standard input can be read") stops the program with that message if reading fails, which is rare; a
wrong answer from a person is not a reading error and needs a different reaction, as the next sections show.
Checkpoint
Check: Reading a line with read_line
parse returns a Result, and refuses more than you might expect
text.parse::<u32>() turns text into a u32, if it can. The type after parse says what to read, or a type on
the variable does, let km: u32 = text.parse()…. It returns a
A value that is either Ok(value), when an operation worked, or Err(error), when it did not, with the reason. The module on errors covers it in full.:
Ok(42) when the text is a number of that type, Err with the reason when it is not. The same 14 texts, parsed
three ways:
// The same text parsed as three types: the value if it parsed, Err if it did not.
fn main() {
println!("{:<9} {:<5} {:<5} f64", "text", "i32", "u32");
for text in ["42", " 42 ", "42\n", "+5", "-0", "-7", "007", "1_000", "1,000", "4e3", "NaN", "inf", "", "300"] {
let as_i32 = match text.parse::<i32>() {
Ok(n) => n.to_string(),
Err(_) => "Err".to_string(),
};
let as_u32 = match text.parse::<u32>() {
Ok(n) => n.to_string(),
Err(_) => "Err".to_string(),
};
let as_f64 = match text.parse::<f64>() {
Ok(x) => x.to_string(),
Err(_) => "Err".to_string(),
};
println!("{:<9} {as_i32:<5} {as_u32:<5} {as_f64}", format!("{text:?}"));
}
} Output
text i32 u32 f64 "42" 42 42 42 " 42 " Err Err Err "42\n" Err Err Err "+5" 5 5 5 "-0" 0 Err -0 "-7" -7 Err -7 "007" 7 7 7 "1_000" Err Err Err "1,000" Err Err Err "4e3" Err Err 4000 "NaN" Err Err NaN "inf" Err Err inf "" Err Err Err "300" 300 300 300
Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O parse_table.rs && ./parse_table
Space at either end and a line break fail every type, so trim first. "+5" is a valid u32 and "-0" a valid i32,
while "-7" and "-0" are refused by u32. Digit separators are refused too: 1_000 is Rust code, not input, and
1,000 a way of writing. "4e3", "NaN" and "inf" are refused as integers but parse as f64, so a successful
parse is not yet a sensible value: NaN is not a price. The errors carry their reason, such as “invalid digit found
in string” or “cannot parse integer from empty string”. For integers, parse reads decimal digits only;
from_str_radix reads other bases, such as hexadecimal.
Checkpoint
Check: What parse accepts and refuses
A reading loop: lines(), trim() and parse() on every line
io::stdin().lines() gives the lines of standard input one at a time, each without its line break, both \n and the
\r\n of files saved on Windows, and ends at the end of input. This loop adds up distances and reports every line it
cannot use:
// Adds up distances, one a line. lines() drops each line break, trim() drops spaces, and parse() may still say no.
use std::io;
fn main() {
let mut total: u32 = 0;
for (i, line) in io::stdin().lines().enumerate() {
let line = line.expect("standard input is text");
match line.trim().parse::<u32>() {
Ok(km) => total += km,
Err(e) => println!("line {}: {line:?} skipped ({e})", i + 1),
}
}
println!("{total} km in all");
} Input (standard input)
120 45 forty 300
Output
line 3: "" skipped (cannot parse integer from empty string) line 4: "forty" skipped (invalid digit found in string) 465 km in all
Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O sum_km.rs && ./sum_km
match tries its arms in order: Ok(km) when parse worked, which adds the number, and Err(e) when it did not,
which prints the line and the reason. Step through the five lines and see where each goes:
The drawing shows the whole scene; its 7 steps are listed below.
Every step as text
- Line 1.
lines()gives the first line,"120", already without its line break.trim()finds no spaces to cut. - A number.
parse::<u32>()reads 120 and returnsOk(120), so theOk(km)arm of the match adds it: the total is 120. - Line 2, with a space. The second line is
" 45". The space in front would makeparsefail, buttrim()has cut it, soparsegets"45"and the total becomes 165. - An empty line. Line 3 is empty. Trimmed it is still
"", andparsereturns an error, "cannot parse integer from empty string". TheErrarm prints that reason and the loop goes on. - A word. Line 4 is
"forty".parsestops at the f with "invalid digit found in string": the number in words is skipped too, with its reason. - Line 5.
"300"parses asOk(300), and the total becomes 465. - The end of the input. The file has no sixth line, so
lines()returnsNoneand theforloop ends. The program prints465 km in all, with the two skipped lines reported above it.
The space before 45 was harmless, because trim() cut it; the empty line and “forty” were reported and skipped,
and the total is 465, not a crash. Use lines() to work through all of the input, and read_line when the program
asks a question and waits for one answer.
Key idea
Read a line, trim it, then parse it, and give every Err a reaction of its own: a message, a second question or
an error for the caller, never a crash on what a person typed.
Checkpoint
Check: A loop of lines, trim and parse
Interactive input that asks again instead of crashing Pro: opens with a Pro, Premium or Ultimate pass
A program that talks to a person must survive every answer that person can type. This part reproduces the crash
that unwrap causes, builds a prompt that asks again, and compares the ways to react to a parse error.
In this part:
- unwrap on input a person typed, reproduced as a crash
- A bill splitter that asks again on every answer it cannot use
- A message for each kind of parse error
- Choosing a reaction to a parse error, from expect to a Result from main
6 runnable examples (Python, Rust) · about 6 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Parsing amounts of money into paise, with the edge cases tested Pro: opens with a Pro, Premium or Ultimate pass
Amounts of money arrive as text such as “1,234.50”, and parse::<f64>() would accept “NaN” and can lose a paisa.
This part writes a parser that accepts only what an amount can be, with a reason for every refusal, and then reads
other bases and whole files.
In this part:
- Exercise: parse an amount of rupees into whole paise
- Other bases with from_str_radix, and a whole input at once
2 runnable examples (Rust) · about 4 minutes
Opening this part with your pass…Pro part. It opens with the same pass as the part above.
Practise: reading lines and parsing numbers
These questions are about read_line, what parse accepts and the reading loop. Every choice comes with the reason
it is right or wrong.
Practice that adapts to you
Practise this lesson
10 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher): what does str::parse return, and why do you trim a line before parsing it?
parse returns a Result: Ok(value) when the whole text is a value of the type asked for, and Err with the
reason when it is not, so the caller must decide what to do with a bad input. "42".parse::<u32>() gives Ok(42).
A line read with read_line still ends with its line break, and people type spaces, but parse accepts no character
that is not part of the number, so "42\n" and " 42 " are both errors. trim() removes white space at both ends,
the line break included, so line.trim().parse::<u32>() reads what the person meant.
-
Fresher to mid
A command-line tool reads one number per line with read_line and strips the line break with trim_end_matches('\n') before parsing. It passes every test on Linux, but users with files saved on Windows get an error for every line. Why, and what is the robust way to read the lines?
-
Mid to senior
An order API reads ?qty= with parse::<i32>() and rejects anything that fails to parse. In production, orders arrive with quantities of -3, 0 and 2147483647, and a refund job pays out on the negative one. What validation is missing, and how would you design it?
-
Fresher to mid
A teammate's tool asks for an amount with read_line and converts it with .trim().parse::<u32>().unwrap(). Users who type "ten" see a panic message and exit status 101. How would you redesign the input handling, for a person at a prompt and for a batch run that reads a file?
Key takeaways
read_lineappends a line to aString, line break included, returns how many bytes it read and returns 0 at the end of input, so clear theStringbefore each read.parsereturns aResult, refuses spaces, line breaks and digit separators, and accepts"+5"as au32and"NaN"as anf64, so trim first and check that the value makes sense.lines()gives each line without its line break, and a loop of trim, parse andmatchreports bad lines instead of crashing.
Terms in this lesson
- end of input
- The point where standard input has nothing more to give, because a file or a pipe has ended or the terminal's input was closed. read_line then returns Ok(0), and lines() returns None.
- standard input
- The stream a program reads its input from: the keyboard when it runs in a terminal, or a file or another program's output when the shell redirects it, as in ./app < answers.txt. Rust reads it through std::io::stdin().
References
- The Rust Standard Library: std::io::Stdin (The Rust Project)
- The Rust Standard Library: std::io::BufRead (read_line and lines) (The Rust Project)
- The Rust Standard Library: str::parse (The Rust Project)
- The Rust Standard Library: std::num::ParseIntError (The Rust Project)
- The Rust Standard Library: std::num::IntErrorKind (The Rust Project)
- The Rust Reference: The non_exhaustive attribute (The Rust Project)
- The Rust Standard Library: u8::from_str_radix (The Rust Project)
- The Rust Standard Library: char::to_digit (The Rust Project)
- The Rust Standard Library: std::io::read_to_string (The Rust Project)
- The Rust Standard Library: print! (The Rust Project)
- The Rust Standard Library: std::process::Termination (The Rust Project)
- The Rust Programming Language: Programming a Guessing Game (The Rust Project)
- Rust error codes index: E0277 (The Rust Project)
- Rust error codes index: E0004 (The Rust Project)
- Input Validation Cheat Sheet (OWASP Foundation)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress