Введение: почему Rust не даёт просто так взять и конвертировать строку в число?
Если вы пришли из Python или JavaScript, то привыкли к магии вроде int("42") или parseInt("42"). В Rust такой фокус не пройдёт — компилятор потребует чётко указать, в какой именно тип вы хотите превратить строку. Почему? Потому что Rust — это язык, который не прощает небрежности. Он заставляет вас думать о граничных случаях: что если в строке окажется не число? Что если число слишком большое для выбранного типа?
В этой статье мы разберём самый популярный вопрос на Stack Overflow среди новичков в Rust: как преобразовать String в int. Вы получите не просто готовый код, но и полное понимание того, что происходит под капотом.
Метод parse() — главный инструмент конвертации
Основной способ преобразования строки в число в Rust — это метод parse(), который доступен для типа String и &str. Он реализован через типаж FromStr и возвращает Result<T, E>.
Базовый пример: String → i32
fn main() {
let number_string = String::from("42");
let number: i32 = number_string.parse().unwrap();
println!("Число: {}", number); // Вывод: Число: 42
}
Обратите внимание на unwrap(). Это быстрый способ «достать» значение из Result, но он опасен: если строка не является числом, программа запаникует и упадёт.
Правильная обработка ошибок
В реальном коде вы никогда не должны использовать unwrap() бездумно. Вот как делать правильно:
use std::num::ParseIntError;
fn main() {
let input = String::from("hello");
match input.parse::<i32>() {
Ok(number) => println!("Успешно: {}", number),
Err(e) => println!("Ошибка конвертации: {}", e),
}
}
Или более лаконичный вариант с if let:
fn main() {
let input = String::from("2024");
if let Ok(year) = input.parse::<i32>() {
println!("Год: {}", year);
} else {
println!("Не удалось распарсить строку");
}
}
Доступные целочисленные типы для конвертации
Rust предлагает богатый выбор целочисленных типов. Вы можете конвертировать строку в любой из них:
i8,i16,i32,i64,i128— знаковые целыеu8,u16,u32,u64,u128— беззнаковые целыеisize,usize— размер зависит от архитектуры
Пример конвертации в разные типы:
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
// А вот так будет ошибка — 255 не влезает в i8
// let d: i8 = val.parse().unwrap(); // PANIC!
}
Продвинутые техники: from_str и пользовательские типы
Метод parse() внутри вызывает from_str() из типажа FromStr. Вы можете реализовать этот типаж для своих структур:
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 {
// Создаём свою ошибку, но для примера используем ParseIntError
return Err("1.0".parse::<u32>().unwrap_err());
}
let major = parts[0].parse::<u32>()?;
let minor = parts[1].parse::<u32>()?;
Ok(Version { major, minor })
}
}
fn main() {
let version_str = String::from("3.14");
let version: Version = version_str.parse().unwrap();
println!("{:?}", version); // Version { major: 3, minor: 14 }
}
Работа с большими числами и переполнением
Если число в строке превышает допустимый диапазон типа, parse() вернёт ошибку, а не обрежет значение молча. Это важное отличие от некоторых других языков:
fn main() {
let big_number = "99999999999999999999"; // 20 цифр
match big_number.parse::<i32>() {
Ok(n) => println!("{}", n),
Err(e) => println!("Ошибка: {}", e), // number too large to fit in target type
}
// А с i128 всё ок
let huge: i128 = big_number.parse().unwrap();
println!("{}", huge); // 99999999999999999999
}
Парсинг строк с пробелами и префиксами
Метод parse() чувствителен к формату. Пробелы или префиксы 0x (шестнадцатеричные) не поддерживаются по умолчанию:
fn main() {
// Это не сработает:
// let a: i32 = " 42 ".parse().unwrap(); // Ошибка!
// let b: i32 = "0x1F".parse().unwrap(); // Ошибка!
// Решение: предварительная очистка
let dirty = String::from(" 42 ");
let clean = dirty.trim();
let number: i32 = clean.parse().unwrap();
println!("{}", number); // 42
// Для шестнадцатеричных используйте специальные методы
let hex = "1F";
let value = i32::from_str_radix(hex, 16).unwrap();
println!("{}", value); // 31
}
Сравнение с другими языками: таблица для наглядности
Чтобы закрепить, вот как выглядит конвертация строки в число в разных языках:
// Python — магия, но без типов
number = int("42")
// JavaScript — тоже магия
let num = parseInt("42");
// Rust — явно и безопасно
let number: i32 = "42".parse().unwrap();
Каждый подход имеет свои плюсы и минусы. Rust выбирает явность и безопасность ценой дополнительной строки кода.
Частые ошибки и как их избежать
- Забыли указать тип:
"42".parse()— компилятор не может вывести тип, нужно аннотировать. - Не