2

When creating a fixed size arrays from existing arrays, it can be tedious to read and write, eg:

let foo: [i32; 8] = [
    bar[1], bar[2], bar[3], bar[4],
    taz[1], taz[2], taz[3], taz[4],
];

Besides using a for loop to assign values, does Rust provide a way to write this without manually expanding the array?

For example, something like Python's unpacking generalizations:

let foo: [i32; 8] = [*bar[1..5], *taz[1..5]];

Note: that the real world example used more items, just keeping it short for the example.


Using vectors this is possible, however moving from a fixed size array to vectors doesn't come for free (tested this and it generates considerably more assembly in release-mode, which performs the transformations as written, using heap memory where the simplistic version only needs stack memory).

let foo: Vec<i32> = bar[1..5].iter().chain(taz[1..5].iter()).cloned().collect();
ideasman42
  • 42,413
  • 44
  • 197
  • 320
  • 3
    *it generates considerably more assembly in release-mode* — that doesn't really mean anything. It's not uncommon for longer code to be faster; perhaps a loop was unrolled for example. What does **profiling ** say is faster? – Shepmaster Sep 22 '16 at 11:04
  • While in general, agree that profiling is best before assuming generated assembly is better/worse. From reading over the ASM in both cases - its clear the Vector version of this code doesn't optimize out the intermediate steps and is infact creating multiple internal data structures and converting them. Thats compared to the simple version of the function which is already manually unrolled. There is no need to use heap memory here, if this were a C program, it would be like using multiple malloc's and memcpy's when there is no need. – ideasman42 Sep 27 '16 at 23:47

2 Answers2

2

For curiosity, here's an array concatenation macro that supports unpacking (limited to a finite number of sizes, this macro is small and can be expanded).

I have nothing to claim about the performance or zero-costiness of this compared to any other solution (profile to know). If you use fixed size arrays all the way, all bounds checking happens at compile time.

First usage example:

fn main() {
    let data = [0, 1, 2, 3, 4, 5, 6, 7, 8];

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

    println!("{:?}", concat_arrays!(data[3..];3 [-1] data;3));
    // [3, 4, 5, -1, 0, 1, 2]

    // let's look at the macro expansion of the last one
    println!("{}", concat_arrays!(@build stringify [] data[3..];3 [-1] data;3));
    // [ data[3..][0] , data[3..][1] , data[3..][2] , -1 , data[0] , data[1] , data[2] ]
}

Then implementation: (playground link)

/// Concatenate array literals and fixed-size unpacked parts of arrays and slices
///
/// Usage: `concat_arrays!(fragments)`  
/// where each fragment is either an array literal: `[x, y, z]`  
/// or an expression and how many elements to unpack: `expression;N`  
/// where `N` must be an integer literal
///
/// See: https://gitlab.com/snippets/27095
/// for a script to generate a macro supporting many more arguments.
macro_rules! concat_arrays {
    // last step -> build an expression
    (@build as_expr [$($t:expr),*]) => {
        [$($t),*]
    };
    (@build $m:ident [$($t:expr),*]) => {
        $m!([$($t),*])
    };
    (@build $m:ident [$($t:expr),*] [$($e:expr),+] $($more:tt)*) => {
        concat_arrays!(@build $m [$($t,)* $($e),*] $($more)*)
    };
    (@build $m:ident [$($t:expr),*] $e:expr;1 $($more:tt)*) => {
        concat_arrays!(@build $m [$($t,)* $e[0]] $($more)*)
    };
    (@build $m:ident [$($t:expr),*] $e:expr;2 $($more:tt)*) => {
        concat_arrays!(@build $m [$($t,)* $e[0], $e[1]] $($more)*)
    };
    (@build $m:ident [$($t:expr),*] $e:expr;3 $($more:tt)*) => {
        concat_arrays!(@build $m [$($t,)* $e[0], $e[1], $e[2]] $($more)*)
    };
    (@build $m:ident [$($t:expr),*] $e:expr;4 $($more:tt)*) => {
        concat_arrays!(@build $m [$($t,)* $e[0], $e[1], $e[2], $e[3]] $($more)*)
    };
    (@build $m:ident [$($t:expr),*] $e:expr;5 $($more:tt)*) => {
        concat_arrays!(@build $m [$($t,)* $e[0], $e[1], $e[2], $e[3], $e[4]] $($more)*)
    };

    // user facing case
    ($($t:tt)+) => {
        concat_arrays!(@build as_expr [] $($t)+)
    }
}

Applied to your question, I'd do this:

concat_arrays!(bar[1..];4 taz[1..];4)

Which is succinct, but sure, it has some problems like for example being in a syntax that's idiosyncratic of the particular macro, and the problems that stem from the 4 having to be a literal, not only that but a literal from a finite list.


Edited:

See expanded macro, including script to generate a macro supporting a pre-defined number of arguments which can be much larger then the example given here.

bluss
  • 12,472
  • 1
  • 49
  • 48
0

If you don't want to go into unsafe code, I would create a mut Vec<T> and then use extend_from_slice() to incrementally extend it with slices:

fn main() {
    let bar = [1,2,3,4,5,6];
    let taz = [7,8,9,10,11,12];
    let mut foo = Vec::new();
    foo.extend_from_slice(&bar[1..5]);
    foo.extend_from_slice(&taz[1..5]);
}

Afterwards it can be converted to a fixed-length slice with into_boxed_slice(). Or, if you need an array, you can use a function I found in another question:

use std::convert::AsMut;

fn clone_into_array<A, T>(slice: &[T]) -> A
    where A: Sized + Default + AsMut<[T]>,
          T: Clone
{
    let mut a = Default::default();
    <A as AsMut<[T]>>::as_mut(&mut a).clone_from_slice(slice);
    a
}

And then convert the Vec from before as follows:

let fixed: [i32; 8] = clone_into_array(&foo);
Community
  • 1
  • 1
ljedrz
  • 20,316
  • 4
  • 69
  • 97
  • In this example `foo` is a `Vec`, not a fixed size array. – ideasman42 Sep 22 '16 at 08:58
  • Since the question is asking about fixed size arrays, (`[i32; 8]` in this case), this answer could be completed by showing how to get the `[i32; 8]` from the `Vec`. – ideasman42 Sep 22 '16 at 09:13
  • This seems quite over-complicated, for performance critical code, creating multiple intermediate arrays just to save some typing, can't necessarily be justified. Would be curious to know if this can be expressed with zero-overhead. *(expanding via macros or so)*. – ideasman42 Sep 22 '16 at 09:36
  • @ideasman42 I'm afraid that is not possible due to the limitation of macros not evaluating expressions. For instance, macros cannot even count in compile time. But if plugins are an option, then that would become possible. – E_net4 Sep 22 '16 at 11:55
  • Creating an array in code does not correspond to the program creating intermediate arrays in the actual compiled program. The compiler knows how to smash compound values into scalars and chew on them. The kernel of crate `matrixmultiply` uses intermediate arrays, that in the compiled code de facto represent simd registers. – bluss Sep 22 '16 at 13:35