Avatar ·

Как правильно объединить два вектора (Vec) в Rust? Лучшие способы конкатенации

Всем привет! Я только начал изучать Rust и столкнулся с задачей объединения двух векторов. Допустим, у меня есть два вектора чисел: `vec1` и `vec2`. Мне нужно получить третий вектор, который содержит все элементы из первого, а затем все из второго. В Python я бы просто написал `vec1 + vec2`, но в Rust это не работает. Я попробовал несколько способов, но не уверен, какой из них правильный и эффективный. Вот мой код: ```rust let mut vec1 = vec![1, 2, 3]; let vec2 = vec![4, 5, 6]; // Попытка 1: простое сложение (не компилируется) // let result = vec1 + vec2; // Попытка 2: через extend vec1.extend(vec2); println!("{:?}", vec1); // Получил [1, 2, 3, 4, 5, 6], но vec1 изменился // Попытка 3: через цикл let mut result = Vec::new(); for item in &vec1 { result.push(*item); } for item in &vec2 { result.push(*item); } ``` Проблемы: 1. `extend` забирает владение и изменяет исходный вектор, что не всегда удобно. 2. Цикл выглядит громоздко и, наверное, неоптимально. 3. Я слышал про `append` и `concat`, но не понимаю, чем они отличаются. Подскажите, пожалуйста, какой способ считается идиоматичным и производительным в Rust? Нужно ли заботиться о производительности при работе с большими векторами (например, 100 000+ элементов)? И как правильно сделать конкатенацию, если я хочу сохранить исходные векторы неизменными? Буду благодарен за примеры кода и объяснения! <<>>

Лучший способ: использование итераторов и метода chain

Самый идиоматичный и производительный способ объединить два вектора в Rust, сохраняя исходные векторы неизменными — это использовать метод chain из модуля итераторов. Он создаёт новый вектор без изменения исходных данных и работает лениво (lazy), что позволяет избежать лишних аллокаций до момента сборки результата.

let vec1 = vec![1, 2, 3];
let vec2 = vec![4, 5, 6];

let result: Vec<i32> = vec1.iter()
    .chain(vec2.iter())
    .copied()
    .collect();

println!("{:?}", result); // [1, 2, 3, 4, 5, 6]
// vec1 и vec2 остались нетронутыми
println!("{:?}", vec1); // [1, 2, 3]
println!("{:?}", vec2); // [4, 5, 6]

Как это работает:

  • iter() создаёт итератор, заимствующий элементы вектора (не перемещает владение).
  • chain() соединяет два итератора последовательно — сначала все элементы первого, затем все элементы второго.
  • copied() разыменовывает ссылки &i32 в значения i32 (для типов, реализующих Copy). Для сложных типов используйте cloned().
  • collect() материализует итератор в новый Vec.

Если нужно переместить данные (без копирования)

Если исходные векторы больше не нужны, можно использовать into_iter(), который потребляет векторы и передаёт владение элементами:

let vec1 = vec![1, 2, 3];
let vec2 = vec![4, 5, 6];

let result: Vec<i32> = vec1.into_iter()
    .chain(vec2.into_iter())
    .collect();

// vec1 и vec2 теперь недоступны — их данные перемещены
println!("{:?}", result); // [1, 2, 3, 4, 5, 6]

Этот способ эффективнее при работе с большими векторами, так как не требует копирования каждого элемента — данные просто перемещаются в новую память. Однако он забирает владение исходными векторами.

Производительность и альтернативы

  • extend — модифицирует существующий вектор, добавляя элементы из другого. Если вам нужно менять один из векторов, это оптимальный вариант: vec1.extend(vec2); (забирает владение vec2).
  • append — похож на extend, но перемещает все элементы из одного вектора в другой за O(1), очищая исходный вектор: vec1.append(&mut vec2);.
  • concat — работает со срезами: [&vec1[..], &vec2[..]].concat(). Создаёт новый вектор, копируя данные. Менее гибкий, чем chain.

Совет по производительности для больших векторов: Если вы заранее знаете итоговый размер, можно зарезервировать память с помощью Vec::with_capacity() перед extend или chain, чтобы избежать многократных переаллокаций:

let mut result = Vec::with_capacity(vec1.len() + vec2.len());
result.extend(vec1.iter().copied());
result.extend(vec2.iter().copied());

В целом, для большинства задач используйте chain().collect() — это чисто, безопасно и достаточно быстро. Для критичных к производительности участков — extend или append с предварительным резервированием памяти.

<<>>

Альтернативный подход: макрос vec! и деструктуризация

Если вам нужно объединить несколько небольших векторов в один, можно воспользоваться макросом vec! в сочетании с распаковкой (spread operator), доступной через .. в Rust с ночными сборками (nightly), но для стабильной версии есть обходной путь — использовать IntoIterator и extend внутри макроса:

let vec1 = vec![1, 2];
let vec2 = vec![3, 4];
let vec3 = vec![5, 6];

let result = vec![
    vec1, // перемещаем vec1
    vec2, // перемещаем vec2
    vec3  // перемещаем vec3
].into_iter()
.flatten()
.collect::<Vec<_>>();

println!("{:?}", result); // [1, 2, 3, 4, 5, 6]

Этот метод удобен, когда векторов больше двух — вы просто собираете их в один временный вектор векторов, а затем "сплющиваете" (flatten). Недостаток: все исходные векторы потребляются (теряется владение).

Для неизменяемых исходных данных — используйте срезы

Если исходные векторы должны остаться доступными, можно работать со ссылками:

let vec1 = vec![1, 2, 3];
let vec2 = vec![4, 5, 6];

let result: Vec<&i32> = vec1.iter()
    .chain(vec2.iter())
    .collect();

// result содержит ссылки на элементы vec1 и vec2
// исходные векторы не тронуты

Этот подход полезен, когда вам не нужно владеть данными — только читать их. Однако тип результирующего вектора будет Vec<&i32>, что может быть неудобно для дальнейшей работы.

Выбирайте способ в зависимости от того, нужно ли вам сохранить исходные векторы, как часто вы выполняете конкатенацию и насколько критична производительность. Для 80% случаев chain().collect() — идеальное решение.