10

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
  • 7,831
  • 2
  • 24
  • 53
fadedbee
  • 39,680
  • 40
  • 152
  • 270
  • 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

5 Answers5

7

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
  • 7,831
  • 2
  • 24
  • 53
  • 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
  • @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
2

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
  • 39,680
  • 40
  • 152
  • 270
1

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
  • 1,784
  • 1
  • 8
  • 19
  • 2
    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
  • This results in a `Vec`, not in a `[u8; 4]`. – snowflake Jul 20 '21 at 15:24
1

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
  • 800
  • 8
  • 21
1

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
  • 530,615
  • 113
  • 910
  • 808