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();
2
  • 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? Commented Sep 22, 2016 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. Commented Sep 27, 2016 at 23:47

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

Comments

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

5 Comments

In this example foo is a Vec, not a fixed size array.
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.
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 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.
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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.