How to convert a String to a number
Short answer: Call parse on the string and name the number type — "42".parse::<i32>(), or let n: i32 = "42".parse(). It returns a Result; the error is a ParseIntError for integers and a ParseFloatError for floats.
Parse it
Section titled “Parse it”parse is a method on str, so it works on a &str and on a String alike — a String derefs to a &str at the call site:
fn main() { let text = String::from("42");
let as_i32: i32 = text.parse().unwrap(); let as_u8 = text.parse::<u8>().unwrap(); let as_f64 = "3.14".parse::<f64>().unwrap();
println!("i32: {as_i32}"); println!("u8: {as_u8}"); println!("f64: {as_f64}");}Run it with cargo run (cargo’s Compiling and Finished lines are not shown here):
i32: 42u8: 42f64: 3.14Every integer type — i8 through i128, u8 through u128, isize, usize — and both floats implement FromStr, which is what parse calls. The target type has to come from somewhere, and there are two spellings: a let annotation or the turbofish ::<T>(), the same choice as How to tell the compiler the type of an empty Vec. Leave it out and the compiler cannot pick one — error[E0284]: type annotations needed.
The error it returns
Section titled “The error it returns”When the string is not a number, the Err is a ParseIntError and its message says why:
fn main() { for candidate in ["42", "4 2", "forty-two", "", "-7"] { match candidate.parse::<i32>() { Ok(n) => println!("{candidate:?} parsed as {n}"), Err(e) => println!("{candidate:?} failed: {e}"), } }}"42" parsed as 42"4 2" failed: invalid digit found in string"forty-two" failed: invalid digit found in string"" failed: cannot parse integer from empty string"-7" parsed as -7A signed type takes a leading -. "4 2" fails because parse reads the whole string and does not trim: a space is just another invalid character, so text from a file or a terminal wants trim() first — How to trim whitespace and strip characters. Parsing an integer from user input, where trimming and error reporting are the whole job, is a later article in this chapter.
The error also carries a kind you can match on instead of parsing the message: InvalidDigit, Empty, PosOverflow, NegOverflow. {:?} prints it as ParseIntError { kind: InvalidDigit }.
The target type has to be wide enough
Section titled “The target type has to be wide enough”Overflow at parse time is about the type, not the digits:
fn main() { for candidate in ["255", "256", "-1"] { match candidate.parse::<u8>() { Ok(n) => println!("{candidate:?} parsed as {n}"), Err(e) => println!("{candidate:?} failed: {e}"), } } for candidate in ["127", "128", "-128", "-129"] { match candidate.parse::<i8>() { Ok(n) => println!("{candidate:?} parsed as {n}"), Err(e) => println!("{candidate:?} failed: {e}"), } }}"255" parsed as 255"256" failed: number too large to fit in target type"-1" failed: invalid digit found in string"127" parsed as 127"128" failed: number too large to fit in target type"-128" parsed as -128"-129" failed: number too small to fit in target type256 is a well-formed integer that does not fit in a u8, so it is PosOverflow; -1 in an unsigned type is not an overflow at all but InvalidDigit, because a - is not part of an unsigned literal. Arithmetic that overflows while the program runs is a different problem, with a different answer.
Floats
Section titled “Floats”f32 and f64 go through the same method and fail with ParseFloatError:
fn main() { for candidate in ["3.14", "1e3", "inf", "NaN", ".5", "abc"] { match candidate.parse::<f64>() { Ok(n) => println!("{candidate:?} parsed as {n:?}"), Err(e) => println!("{candidate:?} failed: {e}"), } }}"3.14" parsed as 3.14"1e3" parsed as 1000.0"inf" parsed as inf"NaN" parsed as NaN".5" parsed as 0.5"abc" failed: invalid float literalThe float parser takes exponent notation, inf and infinity in any letter case, NaN, and a leading or trailing dot. An empty string is its own kind — ParseFloatError { kind: Empty }, “cannot parse float from empty string” — and there is no overflow error: a literal too large for f64, like 1e400, parses as inf instead of failing.
Handle it without unwrapping
Section titled “Handle it without unwrapping”fn parse_port(text: &str) -> Result<u16, String> { text.parse::<u16>().map_err(|e| format!("{text:?} is not a port: {e}"))}
fn main() { for candidate in ["8080", "99999", "http"] { match parse_port(candidate) { Ok(port) => println!("listening on {port}"), Err(e) => println!("error: {e}"), } }}listening on 8080error: "99999" is not a port: number too large to fit in target typeerror: "http" is not a port: invalid digit found in stringmap_err converts the typed error into whatever the surrounding code reports while keeping the original message. Where a missing number has an obvious default, unwrap_or(0) beats a match. In a function that already returns a compatible Result, ? propagates the same error untouched. unwrap belongs to strings the source can see — a literal or a constant — where the parse cannot fail.
- How to trim whitespace and strip characters — the fix for the leading space
parserejects. - How to tell the compiler the type of an empty Vec — annotation versus turbofish, on another generic call.
- Working with numbers — the chapter this article opens.
The next article in this chapter goes the other way: converting a number back into a String.