В чем разница между String и str в Rust? Когда что использовать?
Ключевое различие: владение и расположение в памяти
Отличный вопрос! Это одна из самых частых тем, с которой сталкиваются новички в 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 в параметрах функций. Компилятор сам подскажет, если нужно владение.