14

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?

yolenoyer
  • 8,797
  • 2
  • 27
  • 61
fadedbee
  • 42,671
  • 44
  • 178
  • 308
  • Similar previous question: https://stackoverflow.com/questions/26757355/how-do-i-collect-into-an-array (Not voting to close as dup because that question is more general, and it makes sense to be able to concatenate two arrays in a manner different than collecting an arbitrary iterator into an array.) – user4815162342 Apr 11 '21 at 08:40
  • 1
    I see someone is preparing the answer for you here https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=84a67bd519c327e28a6a23b16518926b – DenisKolodin Apr 11 '21 at 08:42

6 Answers6

12

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);
}
yolenoyer
  • 8,797
  • 2
  • 27
  • 61
  • Thanks for your answer, it's less bad than the long-hand way that I'm currently doing this: `let whole: [u8; 5] = [left[0], left[1], right[0], right[1], right[2]];`, but with dozens of elements. – fadedbee Apr 11 '21 at 06:09
  • The problem is that the `whole` array is initialized two times, first time by filling it with zeroes, second time by doing the real stuff. It may be solved by using [MaybeUninit](https://doc.rust-lang.org/core/mem/union.MaybeUninit.html), but I don't know how to use it. – yolenoyer Apr 11 '21 at 06:21
  • 1
    @yolenoyer: Like [this](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5097cb9e93238165e685d67a81210d12). – eggyal Apr 11 '21 at 19:14
  • @eggyal Thanks! Why don't you propose it as an answer? It looks to be a better solution – yolenoyer Apr 11 '21 at 19:30
  • @yolenoyer: Generally speaking, I think `unsafe` code should be avoided unless absolutely necessary—especially by those who are new to Rust. I put it as a comment for completeness, not as a suggestion for use. I think your approach is the right one in most circumstances. Hopefully, when `const generics` is stabilised, there will be library methods for doing this. – eggyal Apr 11 '21 at 19:35
  • @eggyal Ok, got it. If interested, my first successful try with `MaybeUninit` is [here](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5b6bac668430ac4cef828e7ba3695b7f), trying to keep the `split_at_mut()` method. It's a bit more verbose than your version, and I'm not sure if it's better than my original answer... – yolenoyer Apr 11 '21 at 19:50
  • Once `const_evaluatable_checked` lands, we can give this a proper function signature: https://stackoverflow.com/questions/67041830/how-to-concatenate-arrays-of-known-lengths/67085709#67085709 – fadedbee Apr 14 '21 at 04:53
6

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.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • Could you explain why we need forget here? – koral Jul 02 '23 at 12:01
  • 1
    @koral We copied the data into a new array using bitwise copies, but the drop checker in the compiler is not aware that we did, so the destructors for each item would be called if we didn't `forget()` the old arrays. This is not an issue in the example above, since integers don't have destructors. We would not need this if we'd assume `T: Copy`, but if we don't make that assumption, we can't make copies of the data without telling the compiler not to drop the data in the source. – Sven Marnach Jul 02 '23 at 19:28
3

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]);
}

Emoun
  • 2,297
  • 1
  • 13
  • 20
  • 4
    Thanks for your answer, but that only works for arrays of identical lengths. (I'd previously updated the question.) I get: `expected an array with a fixed size of 2 elements, found one with 3 elements`. I'll add this error, in full, to the question. – fadedbee Apr 11 '21 at 05:59
  • 3
    This results in a `Vec`, not in a `[u8; 4]`. – snowflake Jul 20 '21 at 15:24
3

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);
}
Sudhir Dhumal
  • 902
  • 11
  • 22
3

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]);
    }
}
fadedbee
  • 42,671
  • 44
  • 178
  • 308
1

As a supplement to Sven Marnach's answer, if you don't mind using nightly features (even unfinished ones), you can do like this:

#![feature(generic_const_exprs)]
#![feature(inline_const)]
#![feature(maybe_uninit_array_assume_init)]

use std::mem::MaybeUninit;

fn concat_arrays<T, const M: usize, const N: usize>(a: [T; M], b: [T; N]) -> [T; M + N] {
    let mut result = [const { MaybeUninit::uninit() }; M + N];
    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);
        MaybeUninit::array_assume_init(result)
    }
}

Note that calling std::mem::forget here is safe because the memory corresponding to a and b has already been moved into result and is no longer being used, so there is no need for additional destruction and cleanup of them when the function exits (when their scope ends). This trick can be performed when pursuing ultimate performance, but std::mem::forget should generally be used with caution since it can also be used to transfer memory ownership (compared to std::mem::ManuallyDrop).

Yu Sun
  • 21
  • 4