substr
)
Различия между String
и &str
Типичные ошибки новичков
Примеры для практики
Упражнение
Строки — один из самых распространённых типов данных в программировании. В Rust есть два основных типа строк: &str
(строковый срез) и String
(владеющая строка).
Разбиение строк нужно, чтобы разделить текст на части (например, слова или поля в CSV). Склеивание — чтобы объединить части в одно целое (например, создать предложение из слов).
Эта глава научит вас основам работы со строками в Rust, включая их разделение и объединение, с примерами для новичков.
&str
: неизменяемый срез строки, обычно используется для литералов (например, "hello"
).
String
: динамическая строка, которая может расти и изменяться, хранится на куче.
Пример:
let literal = "hello"; // это &str
let owned = String::from("hello"); // это String
Метод split
: разбивает строку на части по указанному разделителю и возвращает итератор.
Разделитель может быть символом, строкой или даже пробелом.
Пример: разбить предложение на слова:
let text = "apple banana cherry";
let words: Vec<&str> = text.split(" ").collect();
println!("{:?}", words); // ["apple", "banana", "cherry"]
Важно: collect()
собирает итератор в коллекцию, например, Vec<&str>
.
Другие методы разбиения:
split_whitespace()
: разбивает по любым пробельным символам (пробелы, табы, переносы строк).let text = "apple banana\ncherry";
let words: Vec<&str> = text.split_whitespace().collect();
println!("{:?}", words); // ["apple", "banana", "cherry"]
splitn(n, delimiter)
: разбивает только на первые n
частей.let text = "a,b,c,d";
let parts: Vec<&str> = text.splitn(3, ",").collect();
println!("{:?}", parts); // ["a", "b", "c,d"]
Работа с String
: если у вас String
, нужно сначала получить срез &str
с помощью &
.
let owned = String::from("one,two,three");
let parts: Vec<&str> = owned.split(",").collect();
println!("{:?}", parts); // ["one", "two", "three"]
В Rust строки нельзя просто складывать с помощью +
бездумно, потому что String
владеет памятью, а &str
— нет. Нужно учитывать правила владения.
Способы склеивания:
+
:
String + &str
работает, но забирает владение у первой строки.
let s1 = String::from("hello");
let s2 = " world";
let result = s1 + s2; // s1 больше нельзя использовать
println!("{}", result); // "hello world"
push_str
: добавляет &str
к существующей String
, не забирая владение.let mut s = String::from("hello");
s.push_str(" world");
println!("{}", s); // "hello world"
push
: добавляет один символ.let mut s = String::from("hello");
s.push('!');
println!("{}", s); // "hello!"
join
: склеивает коллекцию строк с разделителем.let words = vec!["apple", "banana", "cherry"];
let result = words.join(", ");
println!("{}", result); // "apple, banana, cherry"
format!
: мощный способ объединять строки.let name = "Alice";
let greeting = format!("Hello, {}!", name);
println!("{}", greeting); // "Hello, Alice!"
substr
)В Rust строки хранятся в формате UTF-8, где каждый символ может занимать от 1 до 4 байт. Поэтому индексация идёт не по символам напрямую, а по байтам или с учётом корректных границ символов (Unicode scalar values). Основной инструмент для извлечения подстрок — это срезы (slices).
[start..end]
)Срезы позволяют взять часть строки, указав диапазон байтовых индексов. Это наиболее близкий аналог substr
.
&string[start..end]
(где start
— начало, end
— конец, не включительно).Пример:
let text = "Hello, world!";
let hello = &text[0..5]; // от 0 до 5 (не включительно)
println!("{}", hello); // "Hello"
let world = &text[7..12]; // от 7 до 12
println!("{}", world); // "world"
Если указать только начало ([start..]
), берётся всё до конца:
let world_and_more = &text[7..];
println!("{}", world_and_more); // "world!"
String
Если у вас String
, а не &str
, нужно взять срез с помощью &
:
let mut owned = String::from("Hello, world!");
let hello = &owned[0..5];
println!("{}", hello); // "Hello"
Если строка содержит многобайтовые символы (например, кириллицу или эмодзи), простые байтовые срезы могут вызвать панику. Для работы с символами (Unicode scalar values) используйте метод .chars()
:
Пример:
let text = "Привет, мир!";
let chars: Vec = text.chars().collect(); // преобразуем в вектор символов
let privet: String = chars[0..6].iter().collect(); // собираем первые 6 символов
println!("{}", privet); // "Привет"
Метод .chars()
возвращает итератор по символам, а .collect()
собирает их в нужный тип (например, String
).
Чтобы взять подстроку по количеству символов, нужно:
.chars()
..get(start..end)
для безопасного извлеченияЕсли вы не уверены в границах и хотите избежать паники, используйте метод .get()
вместо прямого среза. Он возвращает Option<&str>
:
let text = "Hello, world!";
if let Some(substr) = text.get(0..5) {
println!("{}", substr); // "Hello"
} else {
println!("Ошибка: неверные границы");
}
substring
(опционально)substr
можно использовать стороннюю библиотеку substring
из crates.io. Добавьте в Cargo.toml
:
[dependencies]
substring = "1.4.5"
Пример использования:
use substring::Substring;
let text = "Hello, world!";
let hello = text.substring(0, 5);
println!("{}", hello); // "Hello"
let world = text.substring(7, 12);
println!("{}", world); // "world"
substr
let text = "Hello, world!";
let first_three = &text[0..3]; // первые 3 символа
let last_five = &text[text.len()-5..]; // последние 5 символов
println!("{}", first_three); // "Hel"
println!("{}", last_five); // "orld!"
text.len()
возвращает длину строки в байтах.&str[start..end]
или &str[start..]
для простых случаев..get(start..end)
для защиты от паники..chars()
для строк с многобайтовыми символами.substring
для более привычного синтаксиса.String
и &str
&str
неизменяемый, его нельзя модифицировать (например, с помощью push_str
).
String
можно изменять, но для этого она должна быть объявлена с mut
.
Пример:
let mut s = String::from("start");
s.push_str(" end");
println!("{}", s); // "start end"
Попытка использовать +
с двумя String
напрямую:
let s1 = String::from("hello");
let s2 = String::from("world");
// let result = s1 + s2; // Ошибка! Нужен &s2
let result = s1 + &s2;
Забывание про mut
:
let s = String::from("hello");
// s.push_str(" world"); // Ошибка! s не mut
let mut s = String::from("hello");
s.push_str(" world"); // Работает
"name:age:city"
по ":"
и вывести каждую часть:
let data = "Alice:25:Berlin";
let parts: Vec<&str> = data.split(":").collect();
println!("Name: {}, Age: {}, City: {}", parts[0], parts[1], parts[2]);
let words = vec!["Rust", "is", "awesome"];
let sentence = words.join(" ");
println!("{}", sentence); // "Rust is awesome"
Напишите программу, которая:
"first,second,third"
." - "
.Ожидаемый результат: "first - second - third"
.