Как правильно объединить два вектора (Vec) в Rust? Лучшие способы конкатенации
Лучший способ: использование итераторов и метода 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() — идеальное решение.