Avatar ·

Как разбить строку (String или &str) на части в Rust?

Всем привет! Я только начал изучать Rust и столкнулся с задачей, которая в других языках (например, Python или JavaScript) решается одной строчкой. Мне нужно разбить строку по разделителю, например, по пробелу или запятой, и получить массив или вектор с частями. Вот что я пытаюсь сделать: ```rust let text = "apple,banana,grape"; // Хочу получить ["apple", "banana", "grape"] ``` Я пробовал использовать метод `.split()`, но он возвращает какой-то итератор, а не массив. Когда я пытаюсь собрать результат в вектор через `.collect()`, компилятор ругается на типы. Ещё я не понимаю, в чём разница между `String` и `&str` при разбиении — у них разные методы? И что делать, если строка содержит Unicode-символы (например, кириллицу)? Будет ли `.split()` работать корректно? Подскажите, пожалуйста, как правильно и эффективно разбить строку. Желательно с примерами кода для самых частых случаев: разбиение по одному символу, по подстроке, и как потом получить вектор строк или срезов. Заранее спасибо! <<>>

Основной способ: метод .split() и сбор в коллекцию

В Rust для разбиения строки используется метод .split(), который есть как у &str, так и у String (через авторазыменование). Он возвращает итератор, поэтому чтобы получить вектор или массив, нужно использовать .collect().

let text = "apple,banana,grape";
let parts: Vec<&str> = text.split(',').collect();
println!("{:?}", parts); // ["apple", "banana", "grape"]

Обратите внимание: тип элемента вектора — &str (срез исходной строки). Rust не создаёт новые строки, а просто указывает на куски исходной строки. Это эффективно по памяти.

Разбиение по пробелам и удаление пустых частей

Если нужно разбить по пробельным символам и проигнорировать пустые строки (например, при двойных пробелах), используйте .split_whitespace():

let text = "a   b c";
let parts: Vec<&str> = text.split_whitespace().collect();
println!("{:?}", parts); // ["a", "b", "c"]

Метод .split_whitespace() автоматически удаляет пустые результаты.

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

Если разделитель — не один символ, а целая подстрока, .split() тоже работает:

let data = "one--two--three";
let parts: Vec<&str> = data.split("--").collect();
println!("{:?}", parts); // ["one", "two", "three"]

Ограничение количества частей

Метод .splitn() позволяет указать максимальное количество фрагментов:

let text = "a,b,c,d";
let parts: Vec<&str> = text.splitn(3, ',').collect();
println!("{:?}", parts); // ["a", "b", "c,d"]

Работа с Unicode (кириллица)

Метод .split() работает корректно с любыми Unicode-символами, включая кириллицу:

let text = "яблоко,банан,виноград";
let parts: Vec<&str> = text.split(',').collect();
println!("{:?}", parts); // ["яблоко", "банан", "виноград"]

Разница между String и &str

  • &str — это срез (ссылка на часть строки). Метод .split() возвращает &str срезы, которые ссылаются на исходные данные.
  • String — владеющая строка. Если вам нужны новые строки (а не ссылки), используйте .map(|s| s.to_string()):
let text = String::from("a,b,c");
let owned_parts: Vec<String> = text.split(',').map(|s| s.to_string()).collect();
println!("{:?}", owned_parts); // ["a", "b", "c"]

Разбиение строки на символы (графемы)

Если нужно разбить на отдельные символы (с учётом Unicode-графем), используйте .chars():

let text = "привет";
let chars: Vec<char> = text.chars().collect();
println!("{:?}", chars); // ['п', 'р', 'и', 'в', 'е', 'т']

Для работы с графемными кластерами (например, эмодзи) используйте крейт unicode-segmentation.

Важное замечание

Метод .split() возвращает ссылку на исходную строку. Это значит, что исходная строка должна жить дольше, чем полученные срезы. Если это проблема, копируйте в String через .to_string().

Итог

  • Для базового разбиения: text.split(',').collect::<Vec<_>>()
  • Для пробелов: text.split_whitespace()
  • Для ограничения количества: text.splitn(n, pat)
  • Для владеющих строк: добавьте .map(|s| s.to_string())
<<>>

Альтернативный подход: работа с регулярными выражениями

Если разделитель сложный (например, нужно разбить по запятой, точке с запятой или пробелу одновременно), удобно использовать крейт regex.

Добавьте в Cargo.toml:

[dependencies]
regex = "1"

Пример использования:

use regex::Regex;

fn main() {
    let text = "apple; banana,grape  orange";
    let re = Regex::new(r"[,; ]+").unwrap(); // один или более разделителей
    let parts: Vec<&str> = re.split(text).collect();
    println!("{:?}", parts); // ["apple", "banana", "grape", "orange"]
}

Разбиение с удалением пустых строк вручную

Если стандартный .split() оставляет пустые строки (например, при разбиении "a,,b" по запятой), их можно отфильтровать:

let text = "a,,b,c";
let parts: Vec<&str> = text.split(',')
    .filter(|s| !s.is_empty())
    .collect();
println!("{:?}", parts); // ["a", "b", "c"]

Работа с ленивыми итераторами

Если вам не нужна вся коллекция сразу, можно обрабатывать части по одной:

let text = "a,b,c";
for part in text.split(',') {
    println!("Part: {}", part);
}

Это позволяет экономить память при работе с большими строками.