1.12Developing your first program

Last updated June 11, 2026

You have all the pieces: statements, variables, printing, input, and the expression machinery underneath. This lesson assembles them into a complete program, built the way real programs get built, in small steps with the compiler running constantly, wrong turn included.

The problem: ask the user for a whole number, then print that number doubled. Target behavior, with the user typing 4:

Enter a whole number:
4
Twice 4 is 8

Per lesson 0.4, resist the urge to write the whole thing at once. We'll add one capability at a time and compile at every step (cargo run after each change; the steps are cheap and the feedback is instant).

Step 1: a skeleton that talks

fn main() {
    println!("Enter a whole number:");
}

Compiles, runs, prompts. It does nothing else, and that's fine; it's a working program at every moment from here on.

Step 2: read the input

Bring in the recipe from lesson 1.6. Here's the step as a beginner actually types it, which is to say: with something missing.

use std::io;

fn main() {
    println!("Enter a whole number:");

    let mut input = String::new();
    io::stdin()
        .read_line(input)
        .expect("failed to read input");
}
error[E0308]: mismatched types
 --> src/main.rs:8:20
  |
8 |         .read_line(input)
  |          --------- ^^^^^ expected `&mut String`, found `String`
  |          |
  |          arguments to this method are incorrect
  |
help: consider mutably borrowing here
  |
8 |         .read_line(&mut input)
  |                    ++++

We forgot the &mut. Read the error with your 1.7 training: headline says mismatched types; the span shows the argument; the labels say what was expected (&mut String) versus what we passed (String); and the help writes out the exact fix. We don't fully understand why read_line wants &mut (that's lesson 9.2's IOU, on record since 1.6), but the compiler just handed us the correction, and "the error message is the textbook" is how this course works. Apply the fix, recompile: clean.

This wrong turn was staged today, but you'll take it for real within the week. Everyone does. The lesson is the recovery: read, fix, recompile, thirty seconds.

Step 3: turn the text into a number

The input is text; doubling needs a number. One new recipe line, with its IOU box below:

use std::io;

fn main() {
    println!("Enter a whole number:");

    let mut input = String::new();
    io::stdin()
        .read_line(&mut input)
        .expect("failed to read input");

    let num: i32 = input.trim().parse().expect("that wasn't a whole number");
}

What you're taking on credit

parse converts text into a number; it follows trim because "4\n" is not a number but "4" is. The : i32 annotation tells parse which kind of number to make, and .expect(...) stops the program if the user typed potato. The full machinery (lesson 5.6) and the grown-up error handling (chapter 12, where stopping the program stops being acceptable) are both on the books. Until then, this line is the standard incantation for "read a number," and you'll use it in every interactive program through chapter 5.

Compile: it builds, with a warning that num is never used. Correct on both counts. On to using it.

Step 4: the output, three ways

The final statement could go several ways, and the differences are worth a minute each, because this is the first time your program has a design decision in it.

Attempt 1, overwrite the variable:

    let mut num: i32 = input.trim().parse().expect("that wasn't a whole number");
    num = num * 2;
    println!("Twice {} is {}", num / 2, num);

Not good. It works, barely, and look at the contortions: num had to become mut, the original value has to be re-derived by dividing back out, and a variable named num now means "twice what the user typed," which is a small lie that some future line will believe.

Attempt 2, a second variable:

    let num: i32 = input.trim().parse().expect("that wasn't a whole number");
    let doubled = num * 2;
    println!("Twice {num} is {doubled}");

Mostly good. Honest names, no mutation, reads cleanly. If doubled were used again later, this would be the right call. It isn't, so we can go one step tighter.

Attempt 3, an expression where the value is needed:

    let num: i32 = input.trim().parse().expect("that wasn't a whole number");
    println!("Twice {} is {}", num, num * 2);

Preferred. Lesson 1.5 said arguments can be expressions, and this is why that matters: a value used exactly once doesn't need a name, and num * 2 sitting right in the output statement is as readable as the idea itself. The complete program:

use std::io;

fn main() {
    println!("Enter a whole number:");

    let mut input = String::new();
    io::stdin()
        .read_line(&mut input)
        .expect("failed to read input");

    let num: i32 = input.trim().parse().expect("that wasn't a whole number");
    println!("Twice {} is {}", num, num * 2);
}
Enter a whole number:
4
Twice 4 is 8

Author's note

Attempt 1 wasn't a straw man; it's a faithful record of how first drafts come out, mine included. Programs are like essays: nobody writes the good version first, and the people who seem to are the ones who didn't show you the draft. Build it any way that works, get it running, then spend five minutes making it say what it means. That second pass is where most of the craft in this course lives.

Quiz time

Question #1

Extend the preferred solution so it also reports three times the number, like so:

Enter a whole number:
4
Twice 4 is 8
Three times 4 is 12
Show solution
use std::io;

fn main() {
    println!("Enter a whole number:");

    let mut input = String::new();
    io::stdin()
        .read_line(&mut input)
        .expect("failed to read input");

    let num: i32 = input.trim().parse().expect("that wasn't a whole number");
    println!("Twice {} is {}", num, num * 2);
    println!("Three times {} is {}", num, num * 3);
}

Same reasoning as the lesson: each multiple is used once, so each lives as an expression in its output line, and num keeps meaning what the user typed.

That's a full program: input, computation, output, and a recovered mistake. The chapter summary next, and then chapter 2 starts taking programs apart into functions.