ПрограммированиеFullstack разработчик

Как устроена работа с неизменяемыми строками и динамическим типом String в Rust? Чем отличаются String и &str, как работает владение, и как безопасно конвертировать между этими типами?

Проходите собеседования с ИИ помощником Hintsage

Ответ.

История вопроса:

По сравнению с нативными языками (C/C++), Rust строит безопасную работу со строками за счет строгого разделения ссылочных (&str) и владельческих (String) типов. Это избавляет от большинства ошибок, связанных с некорректной памятью, выходом за пределы буфера и double-free.

Проблема:

В отличие от взрослых GC-языков, где любая строка живет в управляемой памяти, в Rust надо четко понимать, кто владеет строкой, как долго она живет, и как не получить dangling reference после модификаций. Работа с UTF-8-строками требует аккуратности при индексации и изменении.

Решение:

В Rust String — это изменяемая, heap-аллоцированная строка, которая владеет своим содержимым. &str — это неизменяемая ссылка на последовательность байт с гарантией UTF-8. При необходимости можно безопасно конвертировать (&str -> String и обратно) с помощью методов std.

Пример кода:

fn main() { let owned: String = String::from("Rust"); let borrowed: &str = &owned; let primitive: &str = "Hello"; // литерал всегда &str // Конвертация &str -> String let s: String = primitive.to_string(); // Конвертация String -> &str let st: &str = &s; println!("{} {} {} {}", owned, borrowed, primitive, st); }

Ключевые особенности:

  • Явное разделение между владением и ссылкой (heap vs slice)
  • Методы safety-конверсий между String и &str эффективны и прозрачны по времени жизни объекта
  • Строковые литералы всегда имеют тип &'static str, а не String

Вопросы с подвохом.

Почему нельзя индексировать строку как s[1] или s[i]?

Rust строки в UTF-8, поэтому индексация недоступна напрямую: s[i] не возвращает i-й символ, а иногда приводит к panic при доступе к некорректной границе байтов. Вместо этого используйте методы .chars().nth(i) или .get(start..end).

Можно ли безопасно модифицировать &str?

Нельзя — &str всегда immutable slice. Для модификаций делайте to_owned/to_string, либо используйте String/Vec<u8>.

Чем принципиально отличается String::from("abc") от "abc".to_string()?

Эти варианты эквивалентны по результату, оба создают String с копированием данных из &str. Разница лишь в стиле: например, to_string реализован через трейт ToString, а String::from контрастнее выражает intent "создать владение".

Типовые ошибки и анти-паттерны

  • Попытка индексировать строку s[1] или s[0] для получения char
  • Неявные конверсии без указания срока жизни: возврат ссылок на временные объекты
  • Использование String там, где достаточно &str (лишняя аллокация)

Пример из жизни

Негативный кейс

Функция принимала String и делала ненужное копирование строки внутри (clone), затем писала slice в другой функции, забывая продлить срок жизни источника. Result: dangling reference & crash.

Плюсы:

  • Аналогично привычным языкам: можно легко получить "копию" строки

Минусы:

  • Потери производительности из-за лишних аллокаций
  • Возможна утечка ссылок на временные значения

Позитивный кейс

Функция принимает &str, если нужны модификации — внутри вызывает .to_string(), снаружи вся логика остается "zero copy". Сроки жизни под контролем, ни одной лишней аллокации.

Плюсы:

  • Высокая производительность
  • Предотвращены ошибки владения

Минусы:

  • Нужно разбираться с lifetime-ами и владением
  • Чуть больше когнитивной нагрузки для новичков