---
title: "5.6 Parsing strings into numbers"
description: "The read-a-number incantation taken apart: trim, parse, the turbofish, and what happens when the user types potato."
url: "https://learnrust.net/chapter-5/parsing-strings-into-numbers/"
last_updated: "2026-06-12"
---

# 5.6 Parsing strings into numbers

Since lesson [1.12](https://learnrust.net/chapter-1/developing-your-first-program/), every interactive program in this course has leaned on one magic line:

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

You were told to copy it faithfully and promised a full teardown "in lesson 5.6." This is that lesson, and the satisfying part is how little is left to teach: the chapter has quietly dismantled the incantation piece by piece, and all that remains is to lay the parts on the table.

## The teardown

`input` is a `String` (lesson [5.3](https://learnrust.net/chapter-5/introduction-to-string/)): owned, growable, and holding whatever the user typed, Enter included, because `read_line` keeps the newline (lesson [1.6](https://learnrust.net/chapter-1/reading-input/)).

`.trim()` returns a `&str` (lesson [5.4](https://learnrust.net/chapter-5/introduction-to-str/)): a view into the same text with the whitespace and that newline skipped, no copying involved.

`.parse()` is the one new part, and it's the border crossing lesson [5.3](https://learnrust.net/chapter-5/introduction-to-string/) flagged: it reads text and produces the *value* the text depicts. `"42"` goes in, `42` comes out.

`.expect(...)` handles the possibility that the text depicts no number at all. More on it below, because the user is owed a demonstration of their power to type `potato`.

`let num: i32` you've understood since lesson [4.9](https://learnrust.net/chapter-4/type-inference-and-annotations/): `parse` can target many types, so *something* must pick one, and the annotation is the evidence inference needs. Delete it and the compiler asks for "type annotations needed," exactly as 4.8 showed.

## The turbofish

Lesson 4.8 also promised a second way to answer the which-type question, "with a syntax the community has nicknamed the *turbofish* for its silhouette." Behold:

```rust
fn main() {
    let typed = "42";
    let n = typed.parse::<i32>().expect("not a number");
    println!("{}", n + 1);
}
```

```
43
```

`parse::<i32>()` tells `parse` its target type directly, right at the call, instead of routing the information through the variable's annotation. Tilt your head: `::<>` is a fish, allegedly turbocharged. The two spellings do the same thing, and the choice is style: the annotation reads better when the variable was getting a type anyway; the turbofish shines when the parsed value is used immediately, with no variable to annotate. You'll see both in the wild, and now neither can surprise you.

Floats parse the same way, which the chapter 4 calculator already exploited (changing one annotation made the recipe read an `f64`):

```rust
fn main() {
    let measurement = "98.6".parse::<f64>().expect("not a number");
    println!("{measurement:.1}");
}
```

```
98.6
```

## When the user types potato

`parse` is a *fallible* operation: the text might not depict a number, and Rust won't pretend otherwise. What `parse` actually returns is a value that says "here's the number" *or* "here's what went wrong," and `expect` is the blunt instrument that unwraps it: hand over the number, or stop the whole program and print my message. The or-value is chapter 12's grown-up error handling story (where stopping the program stops being acceptable); until then, `expect` remains the standard move, and now you get to see it earn its keep:

```rust
use std::io;

fn main() {
    println!("How many cookies?");

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

    let cookies: i32 = input.trim().parse().expect("that wasn't a whole number");
    println!("{cookies} cookies coming up.");
}
```

Run it and type `potato`:

```
How many cookies?
potato
thread 'main' (2124427) panicked at src/main.rs:11:45:
that wasn't a whole number: ParseIntError { kind: InvalidDigit }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```

A panic, anatomy per lesson [3.1](https://learnrust.net/chapter-3/syntax-and-semantic-errors/): your `expect` message at the crash site, plus the underlying cause in debug view (`{:?}` formatting, recognizable by yesterday's lesson). `InvalidDigit` means exactly what it says: `parse` met a character that can't be part of an `i32`.

It's worth knowing how strict the border is. For an `i32`, *all* of these panic: `potato`, `7.5` (a dot is not a digit an integer can contain), `4 2` (inner spaces don't trim away, lesson 1.6 warned you), and even an empty line (`ParseIntError { kind: Empty }`). A minus sign is fine for `i32` but `InvalidDigit` for a `u32`, the never-negative types having opinions about `-7`. And `parse` accepts *only* a clean number with optional sign: no `$`, no commas, no trailing `cookies please`.

> **Warning**
>
> The most common parse panic in beginner code isn't `potato`; it's forgetting `trim`. `read_line` keeps the Enter, `"42\n"` contains a character that isn't a digit, and `InvalidDigit` falls out of an input that *looks* perfect on screen. If a parse fails on input you'd swear is a number, print it with `{:?}` first (lesson [5.5](https://learnrust.net/chapter-5/formatting-strings/)): the debug view shows the `\n` that the normal view politely hides.

## The idiom, fully assembled

One more upgrade and the recipe reaches its final form. The raw input and the parsed number are one concept in two forms, which is lesson [5.2](https://learnrust.net/chapter-5/shadowing/)'s cue exactly, shadow straight through the type change:

```rust
use std::io;

fn main() {
    println!("How old are you?");

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

    let age: u32 = age.trim().parse().expect("that wasn't a whole number");

    println!("Next year you'll be {}.", age + 1);
}
```

`age` the `String` becomes `age` the `u32`, the stringly version drops safely out of reach, and every line reads as English. This exact shape (read, then `let name: type = name.trim().parse().expect(...)`) is how the rest of this course reads numbers, and as of this lesson, every word of it is yours. The packaged version from lesson [2.2](https://learnrust.net/chapter-2/return-values/), a `read_number()` function with the recipe inside, stays the right call the moment a program needs two or more numbers (the [chapter 4 quiz](https://learnrust.net/chapter-4/chapter-4-summary-and-quiz/)'s calculator owes its tidiness to it).

## Quiz time

**Question #1**

For each input typed at the prompt, does `let n: i32 = input.trim().parse().expect("no");` produce a value or a panic? If a value, which?

a) `  42  ` (spaces around it)
b) `-7`
c) `3.0`
d) `12cookies`

<details class="solution">
<summary>Show solution</summary>

a) The value `42`: trim eats the outer spaces and the newline, leaving a clean number.
b) The value `-7`: a leading sign is part of a valid `i32`. (The same input panics if the target were `u32`.)
c) A panic, `InvalidDigit`: the dot isn't valid in an integer, and `parse` targeting `i32` won't round, truncate, or negotiate. Parsing `3.0` needs an `f64` target.
d) A panic, `InvalidDigit`: parse takes the whole trimmed text or nothing; trailing words aren't ignored.

</details>

**Question #2**

Rewrite this fragment to use the turbofish instead of the annotation, leaving the behavior identical:

```rust
let lives: u32 = "3".parse().expect("not a number");
```

<details class="solution">
<summary>Show solution</summary>

```rust
let lives = "3".parse::<u32>().expect("not a number");
```

The type information moves from the variable to the call. Inference then types `lives` as `u32` from the evidence on the right, per lesson [4.9](https://learnrust.net/chapter-4/type-inference-and-annotations/).

</details>

**Question #3**

Write a temperature converter: ask for a temperature in Celsius (decimals allowed), and print the Fahrenheit equivalent to one decimal place. The formula is F = C × 9/5 + 32. A sample run:

```
Temperature in Celsius?
36.6
36.6 C is 97.9 F
```

(Careful with the formula's `9/5`: lesson [4.2](https://learnrust.net/chapter-4/integer-types/) taught you what integer division would do to it.)

<details class="solution">
<summary>Show solution</summary>

```rust
use std::io;

fn main() {
    println!("Temperature in Celsius?");

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

    let celsius: f64 = celsius.trim().parse().expect("that wasn't a number");

    let fahrenheit = celsius * 9.0 / 5.0 + 32.0;
    println!("{celsius} C is {fahrenheit:.1} F");
}
```

The recipe with an `f64` target, the shadowing idiom from this lesson, and `{:.1}` from lesson [5.5](https://learnrust.net/chapter-5/formatting-strings/) keeping the output civilized. Writing `9.0 / 5.0` keeps the arithmetic in float country; `9 / 5` would be integer division, quietly computing 1, and producing conversions that are wrong by exactly enough to be embarrassing.

</details>

That closes the chapter's syllabus: constants, shadowing, both string types, formatting out, parsing in. The summary and quiz will make you use all of it at once, which is, as always, the point.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
