What type conversion is
Every value in Rust has one type, fixed at compile time: i32, f64, String, &str and so on. Rust almost never converts between types behind your back, so when you need a number as text, text as a number, or a float as an integer, you ask for it explicitly. There are three everyday tools:
| Goal | Tool | Example |
|---|---|---|
| number to text | to_string() | 25.to_string() gives "25" |
| number to another numeric type | as | 9.99 as i32 gives 9 |
| text to number | parse() | "2024".parse::<i32>() gives Ok(2024) |
Number to String with to_string
to_string() works on any value that can be displayed, which includes all the numeric types. It returns an owned String, a new piece of text that you can keep, change or return from a function.
let age = 25;
let age_string = age.to_string();
println!("{}", age_string.len()); // 2, the text "25" has two characters
println!("{}", 9.99_f64.to_string()); // 9.99
The original age is untouched: conversion produces a new value and leaves the old one as it was.
Float to integer with as
The as keyword converts between numeric types. From a float to an integer it truncates toward zero: it drops the fractional part and never rounds. That means 9.99 as i32 is 9, not 10, and -9.99 as i32 is -9. When you want rounding, call round() on the float first:
let price: f64 = 9.99;
println!("{}", price as i32); // 9
println!("{}", price.round() as i32); // 10
let neg: f64 = -9.99;
println!("{}", neg as i32); // -9
Casts with as never fail and never panic, which is also their danger. A float outside the range of the target type is clamped to the nearest limit: 1e20_f64 as i32 is 2147483647, and f64::NAN as i32 is 0. Between integer types, as keeps only the low bits that fit: 3_000_000_000_i64 as i32 is -1294967296 and 300_i32 as u8 is 44. Use as when you know the value fits or when losing information is what you want.
String to number with parse
Text can contain anything, so parsing can fail. That is why parse() returns a Result instead of a number: Ok(value) on success and Err(...) on failure.
println!("{:?}", "2024".parse::<i32>()); // Ok(2024)
println!("{:?}", "abc".parse::<i32>()); // Err(ParseIntError { kind: InvalidDigit })
println!("{:?}", "3.5".parse::<f64>()); // Ok(3.5)
unwrap() takes the value out of an Ok, and it panics on an Err, ending the program with an error message. It is fine when the text is known to be valid, such as the literal "2024" in this lesson. For input you don't control, handle the Err case with match or unwrap_or instead.
Telling parse which type you want
parse can produce many types, so the compiler must know which one. There are two ways to say it. You can annotate the variable: let n: i32 = s.parse().unwrap();. Or you can use the "turbofish" on the call: s.parse::<i32>().unwrap(). Without either, the code doesn't compile because the target type can't be inferred.
The exercise, step by step
The starter code has three variables: age (an integer), price (a float) and year (a string slice). Each step converts one of them.
- Text from a number. Call
to_string()onageand store the result inage_string. - Integer from a float. Write
price as i32and store it inprice_integer. It holds9, because the decimal part is dropped. - Number from text. Declare
year_integerwith the annotationi32and assignyear.parse().unwrap(). The annotation tellsparsewhat to produce, andunwrap()extracts the number from theResult.
The solution in Rust
let age = 25;
let price = 9.99;
let year = "2024";
let age_string = age.to_string();
let price_integer = price as i32;
let year_integer: i32 = year.parse().unwrap();
Details worth noticing:
agehas no annotation, so Rust picksi32for the integer literal, andpricebecomesf64for the decimal literal.- Each line creates a new variable. Nothing is converted in place, because a variable keeps its type for its whole life.
- The annotation on
year_integeris what makes the last line compile. Remove: i32and the compiler cannot tell whatparseshould return.
Common mistakes
- Expecting rounding.
as i32truncates, so9.99becomes9. - Parsing text with extra characters.
" 42","3.5"and""all fail to parse asi32. Only digits with an optional leading sign are accepted. - Parsing a value that doesn't fit.
"99999999999".parse::<i32>()is anErr(overflow), whereas casting a larger integer withassilently wraps. - Calling
unwrap()on untrusted input. One bad string panics the whole program. - Assigning across types.
let s: String = age;is a type mismatch error. Rust requires the explicitto_string().
Key takeaways
- Rust converts types only when you ask, and each conversion creates a new value.
to_string()turns a displayable value into an ownedString.asconverts between numeric types: from float to integer it truncates toward zero, clamps out-of-range floats, and wraps between integer types.parse()returns aResult; give it a target type and useunwrap()only on text you trust.
