Строки в Rust
Работа с текстом — одно из первых мест, где Rust ощущается не так, как другие языки, в основном потому, что строковых типов у него на самом деле два. Когда понимаешь, зачем так сделано, путаница исчезает.
&str и String
Заголовок раздела «&str и String»&str(«строковый срез», string slice) — текст, который уже где-то существует, обычно написанный прямо в коде. Размер фиксирован, расти он не может.String— текст, которым ты владеешь и который может расти. К нему можно добавлять, и хранится он в куче (heap).
fn main() { let literal: &str = "привет"; // строковый срез let owned: String = String::from("привет"); // String во владении
println!("{literal} / {owned}");}Простое правило: если ты просто выводишь неизменный текст, "вот такой" (&str) вполне подойдёт. Если текст нужно собирать, дополнять или менять во время работы программы, бери String.
Дополняем String
Заголовок раздела «Дополняем String»fn main() { let mut greeting = String::from("Привет"); greeting.push_str(", мир"); // добавить &str greeting.push('!'); // добавить один char
println!("{greeting}");}Вывод:
Привет, мир!Объединяем строки
Заголовок раздела «Объединяем строки»Строки можно соединять через + или с помощью format! (обычно это понятнее):
fn main() { let first = String::from("Привет, "); let second = String::from("мир!");
let combined = first + &second; // обрати внимание на & перед second println!("{combined}");
// format! часто читается легче: let name = "Феррис"; let greeting = format!("Привет, {name}!"); println!("{greeting}");}Полезные методы строк
Заголовок раздела «Полезные методы строк»Несколько методов, которые нужны постоянно. В этом примере текст на английском, потому что мы смотрим на длину в байтах, а у кириллицы она другая — об этом чуть ниже:
fn main() { let text = " Hello, Rust! ";
println!("{}", text.trim()); // "Hello, Rust!" (без пробелов по краям) println!("{}", text.to_uppercase()); // " HELLO, RUST! " println!("{}", text.to_lowercase()); // " hello, rust! " println!("{}", text.contains("Rust")); // true println!("{}", text.trim().len()); // 12 (длина в байтах) println!("{}", text.trim().replace("Rust", "World")); // "Hello, World!"}Перебираем строку
Заголовок раздела «Перебираем строку»Чтобы пройти по строке символ за символом, используй .chars():
fn main() { for c in "мир".chars() { println!("{c}"); }}Для русского текста это особенно заметно: каждая буква кириллицы в UTF-8 занимает 2 байта. .len() считает байты, а не буквы. Чтобы узнать количество символов, используй .chars().count():
fn main() { let word = "Привет"; println!("байт: {}", word.len()); println!("символов: {}", word.chars().count());}Вывод:
байт: 12символов: 6Практика
Заголовок раздела «Практика»В этих задачах текст на английском: в первой мы считаем байты, а во второй собираем адрес электронной почты, где кириллица не нужна.
1. Приводим ввод в порядок
Заголовок раздела «1. Приводим ввод в порядок»Кто-то ввёл в форму " rUsT is FUN ". Преврати это в rust is great: убери лишние пробелы, переведи всё в нижний регистр и замени fun на great. Затем выведи результат и его длину. Подсказка: методы строк можно вызывать цепочкой, один за другим: text.trim().to_lowercase()....
Показать решение
fn main() { let input = " rUsT is FUN ";
// сначала trim, потом нижний регистр, потом заменяем "fun" (уже в нижнем регистре) let clean = input.trim().to_lowercase().replace("fun", "great");
println!("{clean}"); println!("{} bytes", clean.len());}Вывод:
rust is great13 bytesПорядок важен: если вызвать .replace("fun", "great") до .to_lowercase(), ничего не заменится, потому что в тексте всё ещё написано FUN.
2. Собираем адрес почты
Заголовок раздела «2. Собираем адрес почты»Даны first = "Ada" и last = "Lovelace". Собери String [email protected] и выведи её. Затем выведи, есть ли в ней @. Подсказка: format! может принимать в слоты {} выражения вроде first.to_lowercase().
Показать решение
fn main() { let first = "Ada"; let last = "Lovelace";
let email = format!("{}.{}@example.com", first.to_lowercase(), last.to_lowercase());
println!("{email}"); println!("похоже на настоящий: {}", email.contains('@'));}Вывод:
похоже на настоящий: true&str— заимствованный неизменный текст;String— текст во владении, который может расти.- Дополняй
Stringчерез.push_str()(для текста) или.push()(для одного символа). - Для объединения строк предпочитай
format!, а не+: так понятнее и ничего не перемещается. - Частые методы:
.trim(),.to_uppercase(),.contains(),.replace(),.chars(). .len()считает байты: буква кириллицы — 2 байта. Количество символов даёт.chars().count().
Проверь себя
Заголовок раздела «Проверь себя»Проверь себя
1. Какой строковый тип принадлежит тебе и может расти во время работы программы?
2. Почему на этой странице для объединения больше чем одной-двух строк советуют format!, а не +?
3. Почему строку в Rust нельзя индексировать как text[0]?
4. Какой метод добавляет один символ к String, как в greeting.push('!')?
Результат: 0 / 4
Что почитать
Заголовок раздела «Что почитать»- Хранение закодированного текста UTF-8 со строками — книга «Язык программирования Rust»: официальная глава о
String, включая то, почему нельзя обращаться к символу по позиции (перевод на русский). - Строковые срезы — книга «Язык программирования Rust»: что такое
&strна самом деле — когда разберёшься с заимствованием (перевод на русский). - Строки — Rust на примерах: примеры
Stringи&str, которые можно запустить, а также экранирование и «сырые» строки (частично переведено). String— документация стандартной библиотеки (на английском): все методыString, у каждого — небольшой пример.str— документация стандартной библиотеки (на английском): методы вродеtrim,contains,replaceиsplit, которые работают с любым текстом.