Introduction: Why Rust Doesn't Just Let You Convert a String to a Number?
If you come from Python or JavaScript, you're used to magic like int("42") or parseInt("42"). In Rust, such a trick won't work — the compiler will require you to clearly specify exactly which type you want to convert the string into. Why? Because Rust is a language that does not forgive carelessness. It forces you to think about edge cases: what if the string contains something that isn't a number? What if the number is too large for the chosen type?
In this article, we will break down the most popular question on Stack Overflow among Rust beginners: how to convert String to int. You will get not just ready-made code, but also a complete understanding of what happens under the hood.
The parse() Method — The Main Conversion Tool
The primary way to convert a string to a number in Rust is the parse() method, which is available for the String and &str types. It is implemented via the FromStr trait and returns Result<T, E>.
Basic Example: String → i32
fn main() { let number_string = String::from("42"); let number: i32 = number_string.parse().unwrap(); println!("Number: {}", number); // Output: Number: 42}Notice the unwrap(). This is a quick way to "extract" the value from Result, but it is dangerous: if the string is not a number, the program will panic and crash.
Proper Error Handling
In real code, you should never use unwrap() mindlessly. Here's how to do it correctly:
use std::num::ParseIntError;
fn main() { let input = String::from("hello"); match input.parse::<i32>() { Ok(number) => println!("Success: {}", number), Err(e) => println!("Conversion error: {}", e), }}Or a more concise version with if let:
fn main() { let input = String::from("2024"); if let Ok(year) = input.parse::<i32>() { println!("Year: {}", year); } else { println!("Failed to parse the string"); }}Available Integer Types for Conversion
Rust offers a rich selection of integer types. You can convert a string to any of them:
i8,i16,i32,i64,i128— signed integersu8,u16,u32,u64,u128— unsigned integersisize,usize— size depends on architecture
Example of conversion to different types:
fn main() { let val = "255"; let a: u8 = val.parse().unwrap(); // 255 let b: i16 = val.parse().unwrap(); // 255 let c: u32 = val.parse().unwrap(); // 255 // But this will be an error — 255 doesn't fit into i8 // let d: i8 = val.parse().unwrap(); // PANIC!}Advanced Techniques: from_str and Custom Types
The parse() method internally calls from_str() from the FromStr trait. You can implement this trait for your own structs:
use std::str::FromStr;use std::num::ParseIntError;
#[derive(Debug)]struct Version { major: u32, minor: u32,}
impl FromStr for Version { type Err = ParseIntError; fn from_str(s: &str) -> Result<Self, Self::Err> { let parts: Vec<&str> = s.split('.').collect(); if parts.len() != 2 { // Creating our own error, but using ParseIntError for the example return Err("1.0".parse::<u32>().unwrap_err()); } let major = parts[0].p