Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

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.

  • Beginner
  • 15 minutes
  • Examples run with Rust 1.99.0 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 6 of 15 minutes free
Free: Each read appends a line to your String, line break and all · returns a Result, and refuses more than you might expect · A reading loop: , and on every line. With a pass: 2 in-depth parts and 3 interview answers, about 9 minutes.

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.rs: two lines, read into one String Rust · read_line.rs
// 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

  1. Question 1 of 2 A loop calls read_line(&mut line) twice and never clears line. The input is 5, a line break, 7, a line break. What does line hold after the second call?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: "5\n7\n"

    The documentation says the previous content of the buffer is kept. Call line.clear() before each read_line, or the second answer is parsed together with the first.

  2. Question 2 of 2 How does a program that reads with read_line know that the input has ended?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: read_line returns Ok(0), zero bytes read

    read_line returns the number of bytes it read. Zero bytes means nothing is left: a file ended, or the terminal's input was closed.

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:

parse_table.rs: one text, three types Rust · parse_table.rs
// 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

  1. Question 1 of 2 What do "+5".parse::<u32>() and "-0".parse::<u32>() return?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Ok(5) and an Err

    Integer parsing accepts an optional + for every type and a - only for signed types, so "-0" is an Err for u32 and Ok(0) for i32.

  2. Question 2 of 2 A price field is read with text.trim().parse::<f64>(). Which input passes the parse but is still not a valid price?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: NaN

    A successful parse means the text has the form of a value, not that the value makes sense. Check the meaning too, for example with x.is_finite() and a range.

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:

sum_km.rs: distances, one a line Rust · sum_km.rs
// 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:

Interactive · stage by stage How sum_km.rs reads its five lines Step through the loop on the recorded input. Watch each line pass through trim and parse, and where the two bad ones go.
How sum_km.rs reads its five lines, step 7 of 7. Showing lines(): the next line (gave None), trim() ("300"), parse::<u32>() (Ok(300)), Ok(km): total += km (total is 465), None: the loop ends (465 km in all). In focus: None: the loop ends.Err: print why, skipnext linelines(): the next linegave Nonetrim()"300"parse::<u32>()Ok(300)Ok(km): total += kmtotal is 465None: the loop ends465 km in all

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. Line 1. lines() gives the first line, "120", already without its line break. trim() finds no spaces to cut.
  2. A number. parse::<u32>() reads 120 and returns Ok(120), so the Ok(km) arm of the match adds it: the total is 120.
  3. Line 2, with a space. The second line is " 45". The space in front would make parse fail, but trim() has cut it, so parse gets "45" and the total becomes 165.
  4. An empty line. Line 3 is empty. Trimmed it is still "", and parse returns an error, "cannot parse integer from empty string". The Err arm prints that reason and the loop goes on.
  5. A word. Line 4 is "forty". parse stops at the f with "invalid digit found in string": the number in words is skipped too, with its reason.
  6. Line 5. "300" parses as Ok(300), and the total becomes 465.
  7. The end of the input. The file has no sixth line, so lines() returns None and the for loop ends. The program prints 465 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

  1. Question 1 of 2 A file saved on Windows ends its lines with \r\n. What does lines() give for the line 42\r\n?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: "42"

    lines() strips \n and \r\n. Code that removes only \n by hand, or a parse of a read_line result without trim, fails on Windows files because of the \r.

  2. Question 2 of 2 sum_km.rs gets the four lines "10", " 20", "x" and "30 ". What total does it print?

    Type a number.

    A hint

    trim cuts spaces at both ends; which line still fails?

    Show the answer: Question 2 of 2

    Answer: 60 km

    " 20" and "30 " parse once trimmed, and "x" is skipped with its reason: 10 + 20 + 30 = 60 km, as the same input gave on Rust 1.99.0.

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.

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.

  1. Question 1 of 10 A person types 42 and presses Enter. What does read_line add to the String?

    Choose one answer.

    Show the answer: Question 1 of 10

    Answer: "42\n"

    read_line appends everything up to and including the line break. trim() removes it before parsing.

  2. Question 2 of 10 The input is 3, a line break, 4, a line break. What do the two lines print?

    Read the code, then choose one answer.

    let mut line = String::new();
    for _ in 0..2 {
        io::stdin().read_line(&mut line).expect("readable");
        println!("{:?}", line.trim().parse::<u32>());
    }
    Show the answer: Question 2 of 10

    Answer: Ok(3), then an Err: invalid digit

    read_line keeps the previous content. The same loop printed Ok(3) and then Err(ParseIntError { kind: InvalidDigit }) on Rust 1.99.0; line.clear() fixes it.

  3. Question 3 of 10 A loop reads with read_line and stops when line is "\n", a blank line. On a file without a blank line at the end it never stops. Why?

    Choose one answer.

    Show the answer: Question 3 of 10

    Answer: At the end of input read_line returns 0 bytes and adds nothing, so line is never "\n" again; test for 0 bytes

    Always check the byte count. A blank line is data; zero bytes is the end.

  4. Question 4 of 10 What type does "42".parse::<u32>() return?

    Choose one answer.

    Show the answer: Question 4 of 10

    Answer: Result<u32, ParseIntError>

    A parse can fail, so it returns Result<u32, ParseIntError>. The caller decides what an Err means.

  5. Question 5 of 10 What does "-7".parse::<u32>() return?

    Choose one answer.

    Show the answer: Question 5 of 10

    Answer: An Err, because a u32 accepts no minus sign

    parse_table.rs printed Err for "-7" as u32 and -7 as i32. Choose the type that matches the values a field may hold.

  6. Question 6 of 10 What does "1_000".parse::<i32>() return?

    Choose one answer.

    Show the answer: Question 6 of 10

    Answer: An Err

    The underscore is code syntax. Input with separators, 1_000 or 1,000, must have them removed by your own code first, if you decide to accept them at all.

  7. Question 7 of 10 A form sends " 42 " with a line break at the end. Which expression reads it as the u8 42?

    Choose one answer.

    Show the answer: Question 7 of 10

    Answer: text.trim().parse::<u8>()

    trim() removes white space at both ends, line breaks included, which is what text from a person needs before parse.

  8. Question 8 of 10 What does each item of io::stdin().lines() hold?

    Choose one answer.

    Show the answer: Question 8 of 10

    Answer: An io::Result<String>: the line without its line break, or a reading error

    Each item is a Result, because reading can fail, and the String it holds has no \n or \r\n at its end.

  9. Question 9 of 10 What does this print when line is "forty"?

    Read the code, then choose one answer.

    match line.trim().parse::<u32>() {
        Ok(km) => total += km,
        Err(e) => println!("{line:?} skipped ({e})"),
    }
    Show the answer: Question 9 of 10

    Answer: "forty" skipped (invalid digit found in string)

    match runs the arm whose pattern fits. Err(e) binds the error, and its Display text says why, as sum_km.rs reported for its line 4.

  10. Question 10 of 10 One program asks a question and waits for the answer; another adds up a whole file of numbers. Which reading call fits each?

    Choose one answer.

    Show the answer: Question 10 of 10

    Answer: read_line for the question, lines() for the file

    The lesson's rule: lines() to work through all of the input, read_line when the program asks a question and waits for one answer.

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.

  1. 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?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. 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?

    Model answer, Pro. First step and follow-ups

  3. 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?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • read_line appends a line to a String, line break included, returns how many bytes it read and returns 0 at the end of input, so clear the String before each read.
  • parse returns a Result, refuses spaces, line breaks and digit separators, and accepts "+5" as a u32 and "NaN" as an f64, so trim first and check that the value makes sense.
  • lines() gives each line without its line break, and a loop of trim, parse and match reports 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

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.