Как превратить Vec<char> в строку в Rust: полный гайд

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

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

Начать курс

Введение: почему эта тема так популярна?

Если вы когда-нибудь писали на Rust, то наверняка сталкивались с ситуацией, когда у вас есть Vec<char> и вам нужно получить из него обычную строку String. На Stack Overflow этот вопрос собрал тысячи просмотров и десятки ответов — и это не случайно.

В Rust работа со строками — отдельное искусство. В отличие от многих языков, где строки — это просто массивы символов, здесь есть String (изменяемая, владеющая строка) и &str (ссылка на строковый срез). А Vec<char> — это вообще вектор четырёхбайтовых символов Unicode. Поэтому просто "сложить" их, как в Python или JavaScript, не получится.

В этой статье мы разберём 4 рабочих способа преобразовать Vec<char> в String, обсудим производительность, типичные ошибки новичков и дадим готовые примеры кода. Поехали!

1. Самый простой способ: iter().collect()

Этот метод — классика Rust. Если вы уже знакомы с итераторами, то знаете, что collect() может собрать практически любую коллекцию. Для строки это выглядит так:

fn main() {
    let chars: Vec<char> = vec!['П', 'р', 'и', 'в', 'е', 'т'];
    let s: String = chars.iter().collect();
    println!("{}", s); // Выведет: Привет
}

Здесь iter() возвращает итератор по ссылкам на символы (&char), а collect() собирает их в String. Компилятор Rust автоматически понимает, что нужно реализовать трейт FromIterator для String.

Важно: если вы напишете chars.into_iter().collect(), то получите то же самое, но вектор будет потреблён (consumed). Выбирайте вариант в зависимости от того, нужен ли вам исходный вектор дальше.

Плюсы и минусы

  • ✅ Минимум кода, читаемо
  • ✅ Работает с любым итератором символов
  • ❌ Немного медленнее, чем специализированные методы (но для 99% случаев разница незаметна)

2. Сборка через String::from_iter()

Этот способ — по сути, то же самое, что и collect(), но в явной форме. Иногда это удобнее для читаемости:

use std::iter::FromIterator;

fn main() {
    let chars: Vec<char> = vec!['R', 'u', 's', 't'];
    let s = String::from_iter(chars);
    println!("{}", s); // Rust
}

Обратите внимание: я передал chars напрямую, а не chars.iter(). Это работает, потому что Vec<char> реализует IntoIterator. Если вам нужно оставить вектор нетронутым, используйте String::from_iter(chars.iter()).

Лично я предпочитаю этот вариант, когда код читают новички — явное имя функции from_iter сразу даёт понять, что происходит.

3. Ручная итерация с push()

Иногда нужно больше контроля. Например, вы хотите добавить префикс или отфильтровать часть символов. Тогда можно сделать так:

fn main() {
    let chars: Vec<char> = vec!['H', 'e', 'l', 'l', 'o'];
    let mut s = String::with_capacity(chars.len());
    
    for c in chars {
        s.push(c);
    }
    
    println!("{}", s); // Hello
}

Я использовал String::with_capacity(), чтобы заранее выделить память — это ускоряет работу, так как не будет лишних реаллокаций при push().

Когда это нужно?

  • Когда вы параллельно делаете другие манипуляции со строкой
  • Когда хотите добавить разделители или изменить регистр на лету
  • Для обучения — чтобы понять, как работают строки "под капотом"

Производительность этого метода практически идентична collect(), если вы правильно задали ёмкость.

4. Низкоуровневый способ: unsafe и работа с байтами

Этот вариант — для тех, кто хочет выжать максимум производительности или работает с большими объёмами данных. Идея в том, что char в Rust — это 4 байта (UTF-32), а строка — это UTF-8. Нам нужно конвертировать каждый символ в его UTF-8 представление.

fn vec_char_to_string(chars: &[char]) -> String {
    let len: usize = chars.iter().map(|c| c.len_utf8()).sum();
    let mut s = String::with_capacity(len);
    
    for &c in chars {
        s.push(c);
    }
    
    s
}

fn main() {
    let chars = vec!['З', 'д', 'р', 'а', 'в', 'с', 'т', 'в', 'у', 'й'];
    println!("{}", vec_char_to_string(&chars)); // Здравствуй
}

⚠️ Внимание: я не использую unsafe в этом примере, потому что String::push() уже безопасно обрабатывает UTF-8. Но если вы хотите работать напрямую с байтами через as_mut_vec() — это уже unsafe. Для 99% задач такой уровень оптимизации избыточен.

5. Частые ошибки и подводные камни

Ошибка 1: Путаница между String и &str

Многие новички пытаются получить &str из Vec<char>. Напрямую это невозможно, потому что &str — это ссылка на уже существующую строку в памяти. Всегда сначала создавайте String, а потом берите от неё срез: let slice: &str = &s;.

Ошибка 2: Забыли про iter()

Если написать chars.collect::<String>() (без iter() или into_iter()), компилятор выдаст ошибку, потому что Vec не является итератором сам по себе. Всегда используйте iter() или into_iter().

Ошибка 3: Неправильная работа с Unicode

Rust-строки — это UTF-8. Если вы работаете с символами, которые занимают больше 1 байта в UTF-8 (например, эмодзи или кириллица), метод len() вернёт количество байт, а не символов. Используйте chars().count() для подсчёта символов.

6. Что выбрать? Сравнение производительности

Блоги

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