Introducción: ¿por qué Rust no permite simplemente convertir una cadena a un número?
Si vienes de Python o JavaScript, estás acostumbrado a la magia de int("42") o parseInt("42"). En Rust, ese truco no funcionará: el compilador te exigirá que especifiques claramente en qué tipo quieres convertir la cadena. ¿Por qué? Porque Rust es un lenguaje que no perdona el descuido. Te obliga a pensar en los casos límite: ¿y si la cadena no contiene un número? ¿Y si el número es demasiado grande para el tipo elegido?
En este artículo, analizaremos la pregunta más popular en Stack Overflow entre los principiantes en Rust: cómo convertir String a int. No solo obtendrá código listo para usar, sino también una comprensión completa de lo que sucede bajo el capó.
El método parse() — la herramienta principal de conversión
La forma principal de convertir una cadena a un número en Rust es el método parse(), que está disponible para los tipos String y &str. Se implementa a través del trait FromStr y devuelve un Result<T, E>.
Ejemplo básico: String → i32
fn main() { let number_string = String::from("42"); let number: i32 = number_string.parse().unwrap(); println!("Número: {}", number); // Salida: Número: 42}Observe el unwrap(). Esta es una forma rápida de "extraer" el valor de un Result, pero es peligrosa: si la cadena no es un número, el programa entrará en pánico y fallará.
Manejo correcto de errores
En código real, nunca debe usar unwrap() sin pensar. Así es como se hace correctamente:
use std::num::ParseIntError;
fn main() { let input = String::from("hello"); match input.parse::<i32>() { Ok(number) => println!("Éxito: {}", number), Err(e) => println!("Error de conversión: {}", e), }}O una variante más concisa con if let:
fn main() { let input = String::from("2024"); if let Ok(year) = input.parse::<i32>() { println!("Año: {}", year); } else { println!("No se pudo analizar la cadena"); }}Tipos de enteros disponibles para la conversión
Rust ofrece una rica selección de tipos de enteros. Puede convertir una cadena a cualquiera de ellos:
i8,i16,i32,i64,i128— enteros con signou8,u16,u32,u64,u128— enteros sin signoisize,usize— el tamaño depende de la arquitectura
Ejemplo de conversión a diferentes tipos:
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 // Esto dará un error — 255 no cabe en i8 // let d: i8 = val.parse().unwrap(); // PANIC!}Técnicas avanzadas: from_str y tipos personalizados
El método parse() internamente llama a from_str() del trait FromStr. Puede implementar este trait para sus propias estructuras:
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 { // Creamos nuestro propio error, pero para el ejemplo usamos ParseIntError return Err("1.0".parse::<u32>().unwrap_err()); } let major = parts[0].p