String vs str в Rust: главные отличия с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

String vs str в Rust: почему новички путаются и как разобраться раз и навсегда

Если вы только начинаете изучать Rust, то наверняка уже столкнулись с ситуацией: компилятор ругается, вы пытаетесь передать String туда, где ожидается &str, или наоборот. Знакомо? Это один из самых популярных вопросов на Stack Overflow (тысячи просмотров!), и не зря — тема действительно фундаментальная для понимания работы с памятью в Rust.

В этой статье мы разберёмся, чем отличаются String и &str, когда какой тип использовать, и как избежать типичных ошибок. Поехали!

Короткий ответ для тех, кто спешит

Если вам нужно прямо сейчас — запомните главное:

  • String — это владеющий, изменяемый, динамический тип данных, который хранится в куче (heap).
  • &str — это неизменяемый срез строки (slice), который ссылается на данные, принадлежащие кому-то другому. Чаще всего встречается как &str.

Теперь давайте копнём глубже.

Что такое String в Rust?

String — это тип, который владеет своими данными. Когда вы создаёте String, память для неё выделяется в куче, и строка может расти, уменьшаться или изменяться.

let mut s = String::from("Привет");
s.push_str(", мир!");
println!("{}", s); // Выведет: Привет, мир!

Здесь s — владелец строки. Когда s выходит из области видимости, память автоматически освобождается (спасибо RAII — Resource Acquisition Is Initialization).

Основные характеристики String:

  • Владеет данными (owning type)
  • Изменяемый (mutable)
  • Хранится в куче
  • Может быть создан из строкового литерала: String::from("текст")
  • Поддерживает конкатенацию, вставку, удаление символов

Что такое &str в Rust?

&str (произносится как "стр-срез" или "стринг-слайс") — это ссылка на последовательность UTF-8 символов, которая принадлежит кому-то другому. Сам по себе &str — это просто указатель на данные и длина.

let greeting: &str = "Привет";
println!("{}", greeting);

В этом примере greeting — это ссылка на строковый литерал, который хранится прямо в бинарном файле программы (в сегменте read-only памяти). greeting не владеет этими данными — он просто на них смотрит.

Основные характеристики &str:

  • Не владеет данными (borrowed type)
  • Неизменяемый (immutable)
  • Хранится либо в бинарнике (строковые литералы), либо в куче (как срез от String)
  • Легковесный — состоит из двух полей: указатель + длина (16 байт на 64-битной системе)
  • Идеален для передачи в функции, когда не нужно изменять строку

Ключевые отличия String и &str (таблица)

Давайте сведём всё в наглядную таблицу:

Характеристика String &str
Владение Владеет данными Заимствует данные
Изменяемость Да (mut) Нет
Где хранится Куча (heap) Куча, стек или бинарник
Размер в стеке 24 байта (ptr + len + cap) 16 байт (ptr + len)
Создание String::from("...") "..." или &s[..]
Основное применение Когда нужно изменять или владеть строкой Когда нужно только читать строку

Когда использовать String, а когда &str?

Золотое правило Rust: всегда используйте &str для параметров функций, если вам не нужно владеть строкой или изменять её. Это делает ваш код более гибким.

Пример: правильный подход

// Плохо: принимает только String
fn greet_owned(name: String) {
    println!("Привет, {}!", name);
}

// Хорошо: принимает и String, и &str
fn greet(name: &str) {
    println!("Привет, {}!", name);
}

fn main() {
    let s = String::from("Анна");
    greet(&s);       // Работает!
    greet("Пётр");   // Работает! (литерал — &str)
}

Обратите внимание: мы передаём &s — ссылку на String, которая автоматически приводится к &str благодаря Deref-преобразованию.

Когда без String не обойтись

fn build_greeting(name: &str) -> String {
    let mut result = String::from("Здравствуйте, ");
    result.push_str(name);
    result.push('!');
    result // Возвращаем владение строкой
}

fn main() {
    let msg = build_greeting("Мария");
    println!("{}", msg);
}

Здесь мы создаём новую строку, поэтому String — единственный правильный выбор.

Типичные ошибки новичков

Ошибка 1: Путаница с конкатенацией

fn main() {
    let s1 = String::from("Hello");
    let s2 = String::from("World");
    let s3 = s1 + &s2; // s1 перемещается, s2 заимствуется
    // println!("{}", s1); // Ошибка! s1 больше недействителен
    println!("{}", s3);
}

Оператор + использует метод add(self, s: &str) -> String. Он потребляет self (первый операнд) и принимает &str для второго.

Ошибка 2: Возврат ссылки на локальную строку

fn bad() -> &str {
    let s = String::from("временная строка                

Блоги

Рекомендаций по книгам