Как объединять векторы в Rust: 5 главных ошибок и их решение

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

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

Начать курс

Введение: векторы — это просто, пока не нужно их склеивать

Представьте: вы пишете на 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);

Блоги

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