How to concatenate arrays of known lengths?

Viewed 3165

I have two arrays of known lengths:

let left: [u8; 2] = [1, 2];
let right: [u8; 3] = [3, 4, 5];

My first attempt:

let whole: [u8; 5] = left + right;

fails with the error:

error[E0369]: cannot add `[u8; 2]` to `[u8; 3]`
  --> /home/fadedbee/test.rs:25:29
   |
25 |         let whole: [u8; 5] = left + right;
   |                              ---- ^ ----- [u8; 3]
   |                              |
   |                              [u8; 2]

Likewise:

let whole: [u8; 5] = left.concat(right);

fails with:

error[E0599]: the method `concat` exists for array `[u8; 2]`, but its trait bounds were not satisfied
  --> /home/fadedbee/test.rs:25:29
   |
25 |         let whole: [u8; 5] = left.concat(right);
   |                                   ^^^^^^ method cannot be called on `[u8; 2]` due to unsatisfied trait bounds
   |
   = note: the following trait bounds were not satisfied:
           `<[u8] as std::slice::Concat<_>>::Output = _`

I'm currently using an expression of the form:

let whole: [u8; 5] = [left[0], left[1], right[0], right[1], right[2]];

but this is dozens of elements for my actual use-case and is prone to typos.

@Emoun kindly pointed out that I'd misused concat.

Trying it properly:

 let whole: [u8; 5] = [left, right].concat();

I get:

error[E0308]: mismatched types
  --> /home/fadedbee/test.rs:32:31
   |
32 |         let whole: [u8; 5] = [left, right].concat();
   |                                     ^^^^^ expected an array with a fixed size of 2 elements, found one with 3 elements
   |
   = note: expected type `[u8; 2]`
             found array `[u8; 3]`

How do I concatenate arrays of known lengths into a fixed length array?

5 Answers

I guess there is a better answer, but you can do like this:

fn main() {
    let left: [u8; 2] = [1, 2];
    let right: [u8; 3] = [3, 4, 5];

    let whole: [u8; 5] = {
        let mut whole: [u8; 5] = [0; 5];
        let (one, two) = whole.split_at_mut(left.len());
        one.copy_from_slice(&left);
        two.copy_from_slice(&right);
        whole
    };

    println!("{:?}", whole);
}

If you want to avoid the overhead of initializing the target array, you can use MaybeUninit() and unsafe code:

let a = [1, 2, 3];
let b = [4, 5];
let concatenated: [u8; 5] = unsafe {
    let mut result = std::mem::MaybeUninit::uninit();
    let dest = result.as_mut_ptr() as *mut u8;
    std::ptr::copy_nonoverlapping(a.as_ptr(), dest, a.len());
    std::ptr::copy_nonoverlapping(b.as_ptr(), dest.add(a.len()), b.len());
    result.assume_init()
};

Once Rust fully supports const generics, it will be possible to put this code in a function. On Nightly, this is already possible today using the generic_const_expr feature:

#![allow(incomplete_features)]
#![feature(generic_const_exprs)]

pub fn concat_arrays<T, const M: usize, const N: usize>(a: [T; M], b: [T; N]) -> [T; M + N] {
    let mut result = std::mem::MaybeUninit::uninit();
    let dest = result.as_mut_ptr() as *mut T;
    unsafe {
        std::ptr::copy_nonoverlapping(a.as_ptr(), dest, M);
        std::ptr::copy_nonoverlapping(b.as_ptr(), dest.add(M), N);
        std::mem::forget(a);
        std::mem::forget(b);
        result.assume_init()
    }
}

Note that we don't need to make any assumptions about T, since all Rust types can be moved by bitwise copy.

You can try this: Basically you will get the vec first, then try to convert it to an array.

use std::convert::TryInto;

pub fn array_concat() {
    let left: [u8; 2] = [1, 2];
    let right: [u8; 3] = [3, 4, 5];
    let whole: Vec<u8> = left.iter().copied().chain(right.iter().copied()).collect();

    let _whole: [u8; 5] = whole.try_into().unwrap();
}

Note: try_into will works with 1.48 (Is there a good way to convert a Vec<T> to an array?)

EDIT:

If you don't want to use copied() then you can try below snippet:

pub fn array_concat3() {
    let a1 = [1, 2, 3];
    let a2 = [4, 5, 6, 7];

    let mut whole: Vec<u8> = a1.iter().chain(a2.iter()).map(|v| *v).collect();
    let whole: [u8; 7] = whole.try_into().unwrap();
    println!("{:?}", whole);
}

Based on @yolenoyer's answer, this works, but requires cargo +nightly test at the moment to enable const_evaluatable_checked.

#![feature(const_generics)]
#![feature(const_evaluatable_checked)]

pub fn concat<T: Copy + Default, const A: usize, const B: usize>(a: &[T; A], b: &[T; B]) -> [T; A+B] {
    let mut whole: [T; A+B] = [Default::default(); A+B];
    let (one, two) = whole.split_at_mut(A);
    one.copy_from_slice(a);
    two.copy_from_slice(b);
    whole
}

#[cfg(test)]
mod tests {
    use super::concat;

    #[test]
    fn it_works() {
        let a: [u8; 2] = [1, 2];
        let b: [u8; 3] = [3, 4, 5];

        let c: [u8; 5] = concat(&a, &b);

        assert_eq!(c, [1, 2, 3, 4, 5]);
    }
}

Your use of the concat() method is not quite right. Here is how you do it:

fn main(){
    let left: [u8; 2] = [1, 2];
    let right: [u8; 2] = [3, 4];
    
    assert_eq!([left, right].concat(), [1,2,3,4]);
}

Related