Перейти к содержимому

Строки в Rust

Работа с текстом — одно из первых мест, где Rust ощущается не так, как другие языки, в основном потому, что строковых типов у него на самом деле два. Когда понимаешь, зачем так сделано, путаница исчезает.

  • &str («строковый срез», string slice) — текст, который уже где-то существует, обычно написанный прямо в коде. Размер фиксирован, расти он не может.
  • String — текст, которым ты владеешь и который может расти. К нему можно добавлять, и хранится он в куче (heap).
fn main() {
let literal: &str = "привет"; // строковый срез
let owned: String = String::from("привет"); // String во владении
println!("{literal} / {owned}");
}
▶ Попробуй сам

Простое правило: если ты просто выводишь неизменный текст, "вот такой" (&str) вполне подойдёт. Если текст нужно собирать, дополнять или менять во время работы программы, бери 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

В этих задачах текст на английском: в первой мы считаем байты, а во второй собираем адрес электронной почты, где кириллица не нужна.

Кто-то ввёл в форму " 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 great
13 bytes
▶ Попробуй сам

Порядок важен: если вызвать .replace("fun", "great") до .to_lowercase(), ничего не заменится, потому что в тексте всё ещё написано FUN.

Даны 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