Avatar ·

Как преобразовывать String, &str, Vec<u8> и &[u8] друг в друга в Rust?

Всем привет! Я недавно начал изучать Rust и постоянно путаюсь с преобразованиями между строками и байтовыми массивами. Проблема возникает из-за того, что в Rust есть разные типы для строк: String, &str, а для байтов — Vec<u8> и &[u8].

Например, я пишу простую программу, которая читает файл, обрабатывает строки и выводит результат. Но я постоянно получаю ошибки компиляции, потому что не могу понять, как правильно конвертировать один тип в другой.

use std::fs;

fn main() {
    let data: Vec<u8> = fs::read("example.txt").unwrap();
    // Как преобразовать Vec<u8> в String?
    let text: String = ???;
    
    // Потом мне нужно передать часть строки в функцию, которая принимает &str
    process_part(&text);
    
    // А потом мне нужно снова преобразовать результат в Vec<u8> для записи в файл
    let output: Vec<u8> = ???;
    fs::write("output.txt", output).unwrap();
}

fn process_part(part: &str) -> &str {
    // какая-то обработка
    part
}

Главная путаница возникает из-за того, что String — это владеющий тип, &str — это заимствованный срез, Vec<u8> — это владеющий вектор байтов, а &[u8] — это заимствованный срез байтов. Как их правильно преобразовывать друг в друга? И в каких случаях нужно использовать as_bytes(), from_utf8(), to_string() и другие методы?

Буду благодарен за подробное объяснение с примерами!

<<>>

Полное руководство по конвертации между String, &str, Vec<u8> и &[u8]

В Rust работа со строками и байтами требует понимания двух ключевых различий: владение vs заимствование и UTF-8 vs произвольные байты. Давайте разберем все преобразования по порядку.

1. String → &str (самое безопасное и частое преобразование)

let s = String::from("Hello");
let slice: &str = &s;      // автоматическое приведение через deref
let slice: &str = s.as_str(); // явный метод

Это бесплатная операция — вы просто заимствуете содержимое строки. String реализует Deref<Target=str>, поэтому &s автоматически становится &str.

2. &str → String

let slice: &str = "Hello";
let owned: String = slice.to_string(); // метод из Display
let owned: String = slice.to_owned();  // явное клонирование
let owned: String = String::from(slice); // из трейта From

Все три способа эквивалентны. Это дорогая операция — выделяется новая память на куче и копируются данные.

3. Vec<u8> → &[u8]

let bytes: Vec<u8> = vec![72, 101, 108, 108, 111]; // "Hello"
let slice: &[u8] = &bytes;       // автоматическое приведение
let slice: &[u8] = bytes.as_slice(); // явный метод

Аналогично строкам — просто заимствование, бесплатно. Vec<T> реализует Deref<Target=[T]>.

4. &[u8] → Vec<u8>

let slice: &[u8] = b"Hello";
let owned: Vec<u8> = slice.to_vec();    // копирование
let owned: Vec<u8> = slice.to_owned();  // эквивалентно
let owned: Vec<u8> = Vec::from(slice);  // из трейта From

Всегда выделяет новую память и копирует данные.

5. String ↔ Vec<u8> (критически важные преобразования)

Здесь нужно помнить: String — это валидный UTF-8, а Vec<u8> — любые байты.

// String → Vec<u8>
let s = String::from("Привет");
let bytes: Vec<u8> = s.into_bytes(); // потребляет String, отдаёт Vec
let bytes: Vec<u8> = s.as_bytes().to_vec(); // если нужно оставить String

// Vec<u8> → String (может упасть!)
let bytes: Vec<u8> = vec![208, 159, 209, 128, 208, 184]; // "При" в UTF-8
let s = String::from_utf8(bytes).expect("Invalid UTF-8"); // может вернуть ошибку

// Безопасная версия с обработкой ошибок:
match String::from_utf8(bytes) {
    Ok(s) => println!("Строка: {}", s),
    Err(e) => println!("Ошибка UTF-8: {}", e),
}

6. &str ↔ &[u8]

// &str → &[u8]
let slice: &str = "Hello";
let bytes: &[u8] = slice.as_bytes(); // бесплатно, просто reinterpret

// &[u8] → &str (может упасть!)
let bytes: &[u8] = b"Hello";
let slice: &str = std::str::from_utf8(bytes).expect("Invalid UTF-8");

7. Полный пример из вашего вопроса

use std::fs;

fn main() {
    // Читаем файл как байты
    let data: Vec<u8> = fs::read("example.txt").unwrap();
    
    // Преобразуем Vec<u8> в String (если файл в UTF-8)
    let text = String::from_utf8(data).expect("Файл должен быть в UTF-8");
    
    // Передаём &str в функцию (автоматическое приведение)
    let result = process_part(&text);
    
    // Преобразуем обратно в Vec<u8> для записи
    let output: Vec<u8> = result.as_bytes().to_vec();
    fs::write("output.txt", output).unwrap();
}

fn process_part(part: &str) -> &str {
    // Обработка строки
    part.trim()
}

Таблица всех преобразований

Исходный типЦелевой типМетодСтоимость
String&str&s или s.as_str()Бесплатно
&strStrings.to_string()Выделение памяти
Vec<u8>&[u8]&v или v.as_slice()Бесплатно
&[u8]Vec<u8>s.to_vec()Выделение памяти
StringVec<u8>s.into_bytes()Бесплатно (перемещение)
Vec<u8>StringString::from_utf8(v)Проверка UTF-8
&str&[u8]s.as_bytes()Бесплатно
&[u8]&strstd::str::from_utf8(s)Проверка UTF-8

Важно: Всегда помните, что преобразование из байтов в строку может завершиться ошибкой, если данные не являются валидным UTF-8. Используйте String::from_utf8_lossy() если хотите заменить невалидные символы на � вместо паники.

<<>>

Альтернативный подход: использование трейтов From и Into

В