Введение: векторы — это просто, пока не нужно их склеивать
Представьте: вы пишете на Rust программу, которая обрабатывает данные. Вы получаете два Vec<i32> и хотите объединить их в один. Казалось бы, что может быть проще? Но каждый, кто задавал этот вопрос на Stack Overflow (а таких тысячи!), знает: в Rust есть минимум 5 способов это сделать, и половина из них — это ошибки, которые либо не скомпилируются, либо сожрут всю производительность.
В этой статье мы разберём популярный вопрос «What is the best way to concatenate vectors in Rust?» и превратим его в практическое руководство. Вы узнаете, как объединять векторы правильно, какие подводные камни ждут новичков и как писать эффективный код на Rust. Поехали!
Почему конкатенация векторов в Rust — не такая уж тривиальная задача?
В отличие от Python или JavaScript, где можно просто написать list1 + list2, Rust требует явно указывать, что вы хотите сделать с памятью. Вектор в Rust — это не просто список, а структура, владеющая данными в куче. Операции с ней могут привести к:
- Переаллокациям (копированию всего буфера).
- Потере владения (move semantics).
- Ненужным клонированиям (если данные большие).
Поэтому выбор метода зависит от контекста: нужно ли вам сохранить исходные векторы, какой тип данных внутри, важна ли скорость.
Топ-5 ошибок при объединении векторов в Rust
Давайте пройдёмся по самым частым граблям, на которые наступают новички (и не только).
Ошибка №1: Использование + для Vec (не скомпилируется)
Самая интуитивная, но неверная попытка:
let v1 = vec![1, 2, 3];
let v2 = vec![4, 5, 6];
let v3 = v1 + v2; // Ошибка! cannot add `Vec<{integer}>` to `Vec<{integer}>`
Оператор + перегружен только для строк и чисел, но не для векторов. Rust не позволяет складывать коллекции «в лоб».
Ошибка №2: Забываем про move и теряем данные
Многие используют append и удивляются, что первый вектор опустел:
let mut v1 = vec![1, 2, 3];
let mut v2 = vec![4, 5, 6];
v1.append(&mut v2);
println!("{:?}", v2); // Пусто! v2 теперь пуст.
append перемещает элементы из второго вектора в первый, оставляя v2 пустым. Это эффективно, но не всегда ожидаемо.
Ошибка №3: Наивное клонирование всего подряд
Когда нужно сохранить оба исходных вектора, новички часто пишут:
let v1 = vec![1, 2, 3];
let v2 = vec![4, 5, 6];
let mut v3 = v1.clone();
v3.extend(v2.clone()); // Два клонирования!
Для больших векторов с String или сложными структурами это убивает производительность. Лучше использовать ссылки или chain.
Ошибка №4: Игнорирование capacity при extend
Простое extend может вызвать множественные переаллокации:
let mut v1 = vec![1, 2, 3];
let v2 = vec![4, 5, 6];
v1.extend(v2); // Если capacity v1 маленький, будет реаллокация.
Если вы знаете итоговый размер, лучше заранее выделить память: v1.reserve(v2.len()).
Ошибка №5: Путаница между extend и append
extend принимает итератор и может клонировать элементы (если передать &vec). append перемещает элементы без клонирования. Выбор неочевиден, пока не прочитаешь документацию.
Лучшие способы объединения векторов в Rust (с примерами)
Теперь, когда мы знаем врага в лицо, разберём правильные подходы. Каждый из них хорош для своего сценария.
Способ 1: extend — универсальный и понятный
Самый распространённый метод. Добавляет элементы из итератора в конец вектора:
let mut v1 = vec![1, 2, 3];
let v2 = vec![4, 5, 6];
v1.extend(v2); // v1 = [1,2,3,4,5,6], v2 больше не доступен (moved)
println!("{:?}", v1);
Если нужно сохранить v2, передавайте ссылку:
v1.extend(&v2); // v2 остаётся живым, но элементы копируются (если Copy)
Способ 2: append — для максимальной производительности
Перемещает все элементы из второго вектора в первый без клонирования. Идеально, когда старый вектор больше не нужен:
let mut v1 = vec![1, 2, 3];
let mut v2 = vec![4, 5, 6];
v1.append(&mut v2); // v1 = [1,2,3,4,5,6], v2 = []
println!("v2 is now: {:?}", v2); // Пусто!
Это самый быстрый способ, так как он просто перекладывает указатели.
Способ 3: chain из итераторов — без выделения памяти
Если вам нужно просто прочитать объединённые данные, а не хранить их в новом векторе, используйте chain:
let v1 = vec![1, 2, 3];
let v2 = vec![4, 5, 6];
let combined: Vec<_> = v1.iter().chain(v2.iter()).collect();
// combined = [1,2,3,4,5,6], v1 и v2 живы
println!("{:?}", combined);
Этот подход не копирует данные, пока вы не вызовете collect. Полезно для фильтрации или маппинга на лету.
Способ 4: concat для срезов (для Copy типов)
Если у вас есть несколько срезов (или векторов, взятых по ссылке), можно использовать concat:
let v1 = vec![1, 2, 3];
let v2 = vec![4, 5, 6];
let v3 = [&v1[..], &v2[..]].concat(); // v3 = [1,2,3,4,5,6]
println!("{:?}", v3);