Avatar ·

В чем разница между String и str в Rust? Когда что использовать?

Привет, коллеги! Я начал изучать Rust и наткнулся на очень запутанную тему — строки. В документации постоянно встречаются `String` и `&str`, но я никак не могу понять, в чем между ними разница и когда какой тип использовать. Я написал небольшой пример: ```rust fn main() { let s1 = "hello"; // это str? let s2 = String::from("hello"); // а это String? println!("{}", s1); println!("{}", s2); // Почему это работает? let s3: &str = &s2; println!("{}", s3); // А это нет? // let s4: String = s1; // ошибка! } ``` Я вижу, что `s1` — это строковый литерал, а `s2` создается через `String::from()`. Но когда я пытаюсь присвоить `s1` переменной типа `String`, компилятор ругается. При этом я могу взять ссылку на `s2` и присвоить её `&str`. Может кто-нибудь объяснить простыми словами: 1. Что такое `String` и что такое `str`? 2. Почему `str` всегда идет с амперсандом (`&str`)? 3. В каких ситуациях нужно использовать `String`, а в каких `&str`? Заранее спасибо за помощь! <<>>

Ключевое различие: владение и расположение в памяти

Отличный вопрос! Это одна из самых частых тем, с которой сталкиваются новички в Rust. Давайте разберемся по порядку.

1. Что такое String?

String — это owned (владеющий) тип. Он:

  • Хранит данные в куче (heap)
  • Имеет три поля: указатель на данные, длину и емкость
  • Может изменяться (расти, уменьшаться) — это изменяемый тип
  • Когда переменная String выходит из области видимости, память автоматически освобождается

2. Что такое str?

str — это сырая строка (string slice). Это неизменяемый тип, который представляет собой последовательность байтов UTF-8. Сам по себе str не имеет известного размера во время компиляции (это DST — Dynamically Sized Type). Поэтому мы никогда не работаем с str напрямую, а только через ссылки &str.

3. Почему &str?

Поскольку размер str неизвестен на этапе компиляции, Rust не может хранить его на стеке. Мы всегда используем ссылку &str, которая представляет собой fat pointer — два слова: указатель на начало данных и их длина.

Вот как это выглядит в памяти:

String (на стеке):          &str (fat pointer):
[ptr] -> "hello" (куча)     [ptr] -> "hello" (куча или код)
[len]                       [len]
[capacity]

4. Примеры использования

fn main() {
    // str — литерал, хранится в бинарнике программы
    let literal: &str = "hello"; 
    
    // String — владение, хранится в куче
    let owned: String = String::from("hello");
    
    // Можно взять срез от String — получаем &str
    let slice: &str = &owned[..2]; // "he"
    
    // Функция, принимающая &str
    fn print(s: &str) {
        println!("{}", s);
    }
    
    // Оба варианта работают!
    print(literal);  // &str
    print(&owned);   // &String автоматически приводится к &str (Deref)
}

Когда что использовать?

  • &str — когда нужно только прочитать строку. Параметры функций, константы, строковые литералы.
  • String — когда нужно владеть строкой, изменять её, передавать между потоками, возвращать из функций.

Почему нельзя присвоить &str в String?

Потому что &str — это просто "взгляд" на чужие данные. Чтобы получить владение, нужно создать новую строку:

let s1: &str = "hello";
let s2: String = s1.to_string(); // или String::from(s1)
let s3: String = s1.to_owned();  // альтернатива

Практический совет: В параметрах функций всегда используйте &str, если не требуется изменять строку. Это делает функцию более гибкой — она может принимать и литералы, и ссылки на String.

<<>>

Простое правило: String — владелец, &str — наблюдатель

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

String: - Вы владеете данными - Можете изменять (добавлять, удалять символы) - Память выделяется в куче - Используйте, когда нужно создать, модифицировать или вернуть строку

&str: - Вы только смотрите на чужие данные - Не можете изменять - Может ссылаться на часть String, литерал или другую строку - Используйте в параметрах функций и когда нужно только читать

Быстрый чек-лист:

// 1. Строковый литерал -> &str
let s: &str = "привет";

// 2. Создать новую строку -> String
let s: String = String::from("привет");

// 3. Взять часть строки -> &str
let slice: &str = &s[0..3];

// 4. Преобразовать &str в String
let s: String = "привет".to_string();

// 5. Преобразовать String в &str (автоматически через ссылку)
let s: &str = &some_string;

Запомните: если сомневаетесь, используйте &str в параметрах функций. Компилятор сам подскажет, если нужно владение.