Skip to content

Beginner · Types, control flow and functions · topic 9 of 50 · 30 min

Mini project: reading input from the user

Explanation

std::io::stdin().read_line(&mut text) reads one line typed by the user into a String. The text includes the newline, so call trim() before parse() turns it into a number. parse can fail, so it returns a Result; match on it to handle bad input instead of crashing.

Why read input?

Programs become useful when they respond to people. In this lesson you build a small interactive tool: a GST calculator that asks for a price. Along the way you meet three ideas you will use everywhere: the standard library, mutable references, and handling errors.

Step 1: bring in the io module

rust
use std::io;
use brings a name into scope so you can write io::stdin() instead of std::io::stdin().

std is the standard library that comes with Rust. io is its input/output module. A module is a named group of code; you will create your own in module 7.

Step 2: read a line

rust
let mut text = String::new();
io::stdin()
    .read_line(&mut text)
    .expect("Failed to read input");
String::new() makes an empty, growable string. read_line adds the typed line to it.
  • String::new() creates an empty String. String is text that can grow; module 4 explains it fully.
  • The variable is mut because read_line changes it.
  • &mut text passes a mutable reference: read_line may change our String without taking it away from us. Module 4 is all about references.
  • read_line returns a Result, which is either Ok or Err. .expect("...") means: if this is an Err, stop the program with this message. That is fine for now; module 6 shows better ways.

Step 3: turn text into a number

If the user types 250 and presses Enter, text holds "250\n" (on Windows "250\r\n"). The newline would break the conversion, so call trim(), which removes spaces and newlines from both ends. Then parse() converts the text into a number. You must say which number type you want.

rust
let price: u32 = match text.trim().parse() {
    Ok(n) => n,
    Err(_) => {
        println!("Please type a whole number.");
        return;
    }
};
parse returns Ok(number) or Err(error). match handles both cases instead of crashing.

match compares a value against patterns and runs the branch that fits. Here, Ok(n) means "it worked, and the number is n". Err(_) means "it failed"; the underscore ignores the error details. You will study match properly in module 5.

Putting it together

rust
use std::io;

const GST_PERCENT: u32 = 18;

fn main() {
    println!("Enter the price in rupees:");
    let mut text = String::new();
    io::stdin().read_line(&mut text).expect("Failed to read input");

    let price: u32 = match text.trim().parse() {
        Ok(n) => n,
        Err(_) => {
            println!("Please type a whole number.");
            return;
        }
    };

    let gst = price * GST_PERCENT / 100;
    println!("Price: {price}, GST: {gst}, Total: {}", price + gst);
}
Type 250 and it prints: Price: 250, GST: 45, Total: 295. Type abc and it politely asks for a number.

Asking again in a loop

To keep asking until the input is valid, wrap the reading in a loop and use continue in the Err branch and break in the Ok branch. Remember to create a new String (or call text.clear()) each time, because read_line adds to what is already there.

Common mistakes

  • Forgetting trim(). parse sees "250\n" and fails every time.
  • Not saying the target type. let n = text.trim().parse().unwrap(); cannot know which number you want; write let n: u32 = ... or parse::<u32>().
  • Reusing the same String in a loop without clear(). The second line is appended to the first.
  • Using unwrap() on user input. One typo crashes the program; use match.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Mini project: reading input from the user · 1/7

Read → Convert → Calculate

Let us make a program that talks to the user. We will build a small GST calculator.

Prefer a video by a teacher? Find free videos on “rust read user input stdin read_line parse guessing game” on YouTube ↗

In real life: A counter tool at a railway reservation desk

A small terminal tool at a reservation counter asks the clerk for the number of passengers and the fare per head, then prints the total. Clerks make typos, so the tool uses match on parse and asks again instead of crashing, and trims the input so stray spaces do not matter.

rust
use std::io;

fn read_number(prompt: &str) -> u32 {
    loop {
        println!("{prompt}");
        let mut text = String::new();
        io::stdin().read_line(&mut text).expect("read failed");
        match text.trim().parse::<u32>() {
            Ok(n) => return n,
            Err(_) => println!("Not a number, try again."),
        }
    }
}

fn main() {
    let people = read_number("Passengers?");
    let fare = read_number("Fare per head?");
    println!("Total fare: Rs {}", people * fare);
}
read_number keeps asking until the clerk types a valid whole number.

Practice (0/3 done)

Task 1 · Question

The user types 42 and presses Enter. What is in text after read_line?

Task 2 · Question

Why does this fail to compile? let n = "5".parse().unwrap();

Task 3 · Practice

Build a number-guessing game: the secret number is 37 (a constant for now). Read guesses in a loop, print "Too low", "Too high" or "Correct!" and count the attempts. Bad input should print a message and ask again, not crash.

Quick quiz

Question 1

1.Why is the String passed as &mut text to read_line?

Question 2

2.What does trim() do?

Question 3

3.What does .expect("msg") do on an Err?

Question 4

4.Which is the best way to handle a typo in user input?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

Why is the String passed as &mut text to read_line?

Answer: So read_line can change it while we keep owning it

A mutable reference lets the function change the String without taking ownership.

What does trim() do?

Answer: Removes spaces and newlines from both ends

trim removes whitespace, including the newline from read_line.

What does .expect("msg") do on an Err?

Answer: Stops the program (panics) with that message

expect panics with your message if the Result is an Err.

Which is the best way to handle a typo in user input?

Answer: match on the parse result and ask again

Matching on Ok and Err lets you recover gracefully.

What happens when JSON.parse gets broken text?Related

Answer: It throws a SyntaxError

JSON.parse throws on bad input, so wrap it in try and catch.

From JSON: the language of APIs

What does atoi("abc") return?Related

Answer: 0, indistinguishable from the input "0"

That is why strtol is preferred.

From Reading strings safely

What type does Math.round(2.5) return?Related

Answer: long

Math.round(double) returns a long (Math.round(float) returns int).

From Type conversion and reading input

What happens with int.Parse("hello")?Related

Answer: Throws a FormatException

Parse throws when the text is not a number. Use TryParse to avoid the crash.

From Operators, conversions and parsing input

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. This is the last topic of “Types, control flow and functions”. The module exam comes next.
  2. Module 2 exam: Types, control flow and functions · 13 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Ownership: Rust's big idea. 50 topics left in the course before the final exam.

← Previous: Control flow: if, loop, while and for