«How do I convert between String, &str, Vec<u8> and &[u8]?» — ваш чек-лист по оптимизации в Rust
Если вы когда-нибудь писали на Rust код, работающий с текстом или байтами, вы наверняка сталкивались с вопросом: «Как мне преобразовать String в &str? А Vec<u8> в &[u8]? И в обратную сторону?». Это один из самых популярных вопросов на Stack Overflow (десятки тысяч просмотров), и неспроста. Кажущиеся простыми преобразования таят в себе подводные камни, особенно когда речь заходит о производительности.
В этой статье мы не просто перечислим способы «как сделать». Мы разберём, почему одни методы быстрее других, и когда стоит использовать каждый из них. Вы узнаете, как избежать ненужных копирований, лишних аллокаций и как писать по-настоящему эффективный код на Rust.
Зачем это знать? Контекст и производительность
Rust — язык, который даёт вам контроль над памятью. Но с контролем приходит ответственность. Преобразование между строками и байтами — одна из самых частых операций. Например, при работе с сетевыми протоколами, сериализацией данных (JSON, Protobuf) или просто чтением файлов. Неправильный выбор конвертации может привести к тому, что вы скопируете данные, которые могли бы использовать без копирования. В мире системного программирования это критично.
Давайте разберём четыре основных типа и научимся летать между ними без потерь.
String и &str: две стороны одной монеты
String — это владеющий, изменяемый, UTF-8 строка. &str — это заимствованный, неизменяемый срез (slice) строки. Главное правило: String — это владелец данных, &str — это ссылка на данные.
Как получить &str из String (и обратно)
Это самый простой и безопасный случай.
let my_string = String::from("Привет, мир!");
// 1. Получаем &str из String (просто заимствуем)
let my_str: &str = &my_string;
// Или с помощью as_str()
let my_str2: &str = my_string.as_str();
// 2. Получаем String из &str (создаём новую копию)
let new_string: String = my_str.to_string();
// Или
let new_string2: String = String::from(my_str);
Важно: Преобразование &str в String всегда создаёт новую выделенную память (аллокацию). Это дорого. Преобразование String в &str — это просто взятие ссылки (бесплатно).
Как получить Vec<u8> из String (и наоборот)
Здесь начинается самое интересное, потому что String внутри — это просто Vec<u8> с гарантией корректного UTF-8.
let my_string = String::from("Rust");
// 3. String -> Vec<u8>
// Вариант А (копирование): безопасно, но дорого
let bytes_vec_copy: Vec<u8> = my_string.as_bytes().to_vec();
// Вариант Б (без копирования, потребление String):
let bytes_vec_owned: Vec<u8> = my_string.into_bytes(); // my_string больше не существует!
// 4. Vec<u8> -> String (проверка на корректность UTF-8)
let new_string: String = String::from_utf8(bytes_vec_owned).expect("Невалидный UTF-8");
// Если вы уверены в валидности:
let new_string_unchecked: String = unsafe { String::from_utf8_unchecked(bytes_vec_copy) };
Ключевой момент: into_bytes() — это zero-cost преобразование. Он берёт внутренний буфер Vec<u8> и отдаёт его вам, не копируя ни байта. as_bytes().to_vec() — это явное копирование.
Метод from_utf8_unchecked — это unsafe. Используйте его только если вы на 100% уверены, что байты — это валидный UTF-8. В противном случае, программа может упасть с паникой или, что хуже, с UB (undefined behavior).
Vec<u8> и &[u8]: байтовые срезы
Здесь логика абсолютно та же, что и с String/&str.
let my_vec: Vec<u8> = vec![72, 101, 108, 108, 111]; // "Hello"
// 5. Vec<u8> -> &[u8] (бесплатно)
let my_slice: &[u8] = &my_vec;
// Или
let my_slice2: &[u8] = my_vec.as_slice();
// 6. &[u8] -> Vec<u8> (создание копии)
let new_vec: Vec<u8> = my_slice.to_vec();
// 7. &[u8] -> &str (без копирования, если это текст)
let text: &str = std::str::from_utf8(my_slice).expect("Не UTF-8");
Запомните простое правило: всегда, когда вам нужно просто посмотреть на данные, используйте срез (&str или &[u8]). Всегда, когда вам нужно владеть данными, используйте String или Vec<u8>.
Сводная таблица: что, когда и сколько стоит
Чтобы вам было проще ориентироваться, вот шпаргалка по производительности конвертации типов в Rust.
| От | К | Метод | Стоимость (память/время) | Примечание |
|---|---|---|---|---|
String |
&str |
&s или s.as_str() |
Ноль (бесплатно) | Просто заимствование |
&str |
String |
.to_string() или String::from() |
O(n) (аллокация + копирование) | Создаёт новый владеющий буфер |
String |
Vec<u8> |
s.into_bytes() |