Step 2 of 7
From text to numbers: parse
Input always arrives as text. When a user types 41, your program receives the characters '4' and '1' (and a newline), not the number 41. To do math, convert the text with parse:
use std::io;
fn main() {
let mut line = String::new();
io::stdin().read_line(&mut line).expect("failed to read a line");
let age: u32 = line.trim().parse().expect("please type a whole number");
println!("Next year you'll be {}", age + 1);
}
41
Next year you'll be 42
.parse()turns text into a value. It can produce many types (i32,u8,f64, evenbool), so it needs to know which one you want. Here the annotation onlet age: u32tells it.- You can also name the type on the method itself with the turbofish
::<>:line.trim().parse::<u32>(). It's handy when there's no variable annotation to go by. - Parsing can fail (
abc,4 2, or-5for au32), soparsereturns aResulttoo, and.expect(...)stops the program with your message if the text isn't a valid number. trim()is essential:"41\n"is not a number,"41"is.
Here's what happens without trim:
use std::io;
fn main() {
let mut line = String::new();
io::stdin().read_line(&mut line).expect("failed to read a line");
println!("read {:?}", line);
let n: i32 = line.parse().expect("not a number");
println!("{n}");
}
41
read "41\n"
The program panics, and the message starts with your text, followed by the reason:
not a number: ParseIntError { kind: InvalidDigit }
The newline counts as an invalid digit. That's why expect messages should say what was expected: it's the first thing you (or a teammate reading a crash report) will see.
The shadowing idiom
Reading into a String and then parsing into a variable with the same name is very common Rust. It's how the official Rust book's guessing game does it:
use std::io;
fn main() {
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("failed to read a line");
let guess: u32 = guess.trim().parse().expect("please type a number");
println!("You guessed {guess}, doubled that's {}", guess * 2);
}
21
You guessed 21, doubled that's 42
The text version isn't needed after parsing, so the number takes over its name.
Decimals
Parse into f64 for numbers with a fractional part: "3.5" works, and so does "7". The decimal separator must be a point: "3,5" fails. Parsing "3.5" into an integer type fails too.
Common mistakes
- Not saying which type to produce:
fn main() {
let n = "42".trim().parse().expect("not a number");
println!("{n}");
}
error[E0284]: type annotations needed
Fix: let n: i32 = ... or .parse::<i32>().
- Forgetting
.expect:let n: i32 = line.trim().parse();givesexpected `i32`, found `Result<_, _>`. TheResultmust be unwrapped first, and the help line suggests.expect(...). - A type that doesn't fit the data.
"-3"doesn't parse asu32(no minus sign allowed), and"300"doesn't fit in au8(number too large to fit in target type). Usei32ori64when values can be negative,f64for decimals.
Your task
The program reads a temperature in Celsius (it may have decimals and may be negative). Parse it as an f64, convert it to Fahrenheit with F = C × 9 / 5 + 32, and print both: the Celsius value with a plain {} (so 0 prints as 0) and the Fahrenheit value with one decimal ({:.1}):
21.5 C = 70.7 F
Challenge 2
Read a number of minutes (a whole number) and print it as hours and minutes:
135 minutes = 2 h 15 min
Challenge 3
Read a year and say whether it's a leap year. A year is a leap year if it's divisible by 4, except years divisible by 100, which are leap years only if they're also divisible by 400. Print a line like 2024 is a leap year or 1900 is not a leap year, with the year that was read.
Previous: Reading a line with read_line Next: Reading several lines