Avatar ·

Как преобразовать строку (String) в целое число (int) в Rust?

Всем привет! Я только начал изучать Rust, перешел с Python, и столкнулся с проблемой преобразования строки в число. В Python я привык писать `int("123")` и получать число 123. Но в Rust всё оказалось сложнее. У меня есть переменная: ```rust let input = String::from("42"); let number: i32 = input; // Ошибка! ``` Компилятор выдаёт ошибку: `expected i32, found String`. Я понимаю, что Rust — строго типизированный язык, и просто так присвоить строку числу нельзя. Я пробовал использовать `as` для приведения типа: ```rust let number: i32 = input as i32; // Тоже ошибка ``` Но это тоже не работает. Я читал, что в Rust нужно использовать метод `parse()`, но не понимаю, как правильно его применить. Что делать, если строка может быть невалидной (например, "abc")? Как обработать ошибку? И как быть, если строка очень большая или содержит пробелы? Буду благодарен за подробный пример с объяснением, как правильно конвертировать `String` в `i32` (или другие целые числа) с учётом обработки ошибок. Заранее спасибо! <<>>

Основной способ: метод parse()

В Rust для преобразования строки в целое число используется метод parse(), который доступен для типа str (строковый срез). Он возвращает Result, поэтому нужно обрабатывать возможную ошибку парсинга.

Вот правильный способ:

fn main() {
    let input = String::from("42");
    
    // Преобразуем String в &str (автоматически через заимствование)
    // и вызываем parse()
    let number: i32 = input.parse().unwrap(); // .unwrap() - упрощённо для примера
    println!("Число: {}", number); // Выведет: Число: 42
}

Но в реальном коде никогда не используйте .unwrap() — это приведёт к панике при невалидном вводе. Вместо этого обрабатывайте Result:

Обработка ошибок (рекомендуемый способ)

fn main() {
    let inputs = vec!["42", " 10 ", "abc", "99999999999999999", "3.14"];
    
    for input in inputs {
        // Метод trim() удаляет пробелы, parse() пытается распарсить
        match input.trim().parse::<i32>() {
            Ok(number) => println!("Успех: '{}' -> {}", input, number),
            Err(e) => println!("Ошибка для '{}': {}", input, e),
        }
    }
    // Вывод:
    // Успех: '42' -> 42
    // Успех: ' 10 ' -> 10
    // Ошибка для 'abc': invalid digit found in string
    // Ошибка для '99999999999999999': number too large to fit in target type
    // Ошибка для '3.14': invalid float literal
}

Детальное объяснение

  • parse() — это generic-метод, тип результата указывается либо через аннотацию типа (: i32), либо через "турбо-рыбу" (::<i32>).
  • trim() удаляет пробелы и непечатные символы в начале и конце строки.
  • Ошибки: parse() вернёт Err если строка содержит нецифровые символы, число выходит за пределы типа, или строка пуста.
  • Для разных типов: i32, u64, i8, usize и т.д. — все они реализуют трейт FromStr, поэтому parse() работает для любых целочисленных типов.

Работа с большими строками и производительностью

Если вам нужно парсить много строк, избегайте создания лишних копий. Используйте &str напрямую:

fn parse_number(s: &str) -> Option<i32> {
    s.trim().parse().ok() // .ok() преобразует Result в Option
}

fn main() {
    let data = "100\n200\nabc\n300";
    let sum: i32 = data
        .lines()
        .filter_map(parse_number)
        .sum();
    println!("Сумма чисел: {}", sum); // 600
}

Метод .ok() преобразует Result<T, E> в Option<T>, отбрасывая ошибки. Это удобно, когда невалидные строки можно просто игнорировать.

<<>>

Альтернативный подход: крейт lexical или ручной парсинг

Для высоконагруженных приложений или специфических форматов можно использовать более производительные крейты, например lexical (сейчас lexical-core):

// Cargo.toml: lexical-core = "0.8"
use lexical_core;

fn main() {
    let input = b"42"; // Работает с байтами, быстрее
    let number: i32 = lexical_core::parse(input).unwrap();
    println!("{}", number);
}

Или, если вам нужно реализовать парсинг вручную (например, для обучения):

fn custom_parse(s: &str) -> Option<i32> {
    let s = s.trim();
    if s.is_empty() { return None; }
    
    let bytes = s.as_bytes();
    let mut result: i32 = 0;
    let mut negative = false;
    let start = if bytes[0] == b'-' { negative = true; 1 } else { 0 };
    
    for &byte in &bytes[start..] {
        if byte < b'0' || byte > b'9' { return None; }
        result = result.checked_mul(10)?.checked_add((byte - b'0') as i32)?;
    }
    
    if negative { result = result.checked_neg()?; }
    Some(result)
}

Этот ручной вариант использует checked_* методы для предотвращения переполнения. Для большинства задач стандартного parse() более чем достаточно — он оптимизирован и покрывает все краевые случаи.