Initialize arrays from smaller arrays

This works, but it's kind of clunky.

pub struct LLVector3 {
    pub x: f32,
    pub y: f32,
    pub z: f32
    }
impl LLVector3 {
    pub fn to_le_bytes(&self) -> [u8;12] {
    let x = self.x.to_le_bytes();
    let y = self.y.to_le_bytes();
    let z = self.z.to_le_bytes();
    [x[0],x[1],x[2],x[3],y[0],y[1],y[2],y[3],z[0],z[1],z[2],z[3]]   // as 12 bytes
    }
}

Is there a more ideomatic way to construct the 12 bytes? I want to concatenate three instances of [u8;4] into a [u8:12], without heap allocation, and this looks like the hard way.

(Part of something does marshaling, obviously.)

You could initialize [0u8; 12], then copy_from_slice each part into it.

If you allow unsafe code, you could transmute([x, y, z]) to go from [[u8; 4]; 3] to [u8; 12]. I think this would also be a case eventually covered by project "safe transmute".

OK, so I'm not missing out on some simple way to do this. I'll keep the long version. Thanks.

The answer to that for many things involving arrays is "no", right now. Things are slowly getting better as we get more and more parts of const generics, but right now what's implemented can't do -> [i32; N + M] that functions for this would need.

One day!

OK, thanks. I'm writing low-level marshaling functions, and more array features would have made them less verbose, but they're all written now and passing tests.

You may want to check out the array-init crate. You could initialize your byte array with an .iter().flatten() over the converted floats or similar.

What about using byteorder::ByteOrder::write_f32() to write the bytes into an array directly?

Maybe something like this:

pub struct LLVector3 {
    pub x: f32,
    pub y: f32,
    pub z: f32,
}

impl LLVector3 {
    pub fn to_le_bytes_byteorder(&self) -> [u8; 12] {
        self.write_with_order::<LittleEndian>()
    }

    fn write_with_order<B>(&self) -> [u8; 12]
    where
        B: ByteOrder,
    {
        let mut buffer = [0; 12];
        B::write_f32(&mut buffer[0..], self.x);
        B::write_f32(&mut buffer[4..], self.y);
        B::write_f32(&mut buffer[8..], self.z);

        buffer
    }
}

See the playground for a more elaborate version that also double-checks the implementations behave identically for some hand-picked values.

You can easily create your own BufferWriter type which wraps a buffer to provide a more ergonomic syntax (e.g. method chaining or whatever).

Interestingly, here is the assembly generated by your version using f32::to_le_bytes():

playground::LLVector3::to_le_bytes: # @playground::LLVector3::to_le_bytes
# %bb.0:
	pushq	%rbp
	pushq	%rbx
	movl	(%rdi), %r8d
	movl	4(%rdi), %edx
	movl	%edx, %esi
	movzbl	%dh, %ebp
	movzbl	%dl, %eax
	movl	%edx, %ecx
	shrl	$16, %ecx
	shrl	$24, %esi
	movl	8(%rdi), %ebx
	movl	%ebx, %edi
	shrl	$24, %edi
	shlq	$24, %rdi
	movzbl	%bl, %edx
	orq	%rdi, %rdx
	movzbl	%bh, %edi
	shrl	$16, %ebx
	movzbl	%bl, %ebx
	shlq	$16, %rbx
	shlq	$8, %rdi
	orq	%rbx, %rdx
	orq	%rdi, %rdx
	shlq	$56, %rsi
	orq	%r8, %rsi
	movzbl	%cl, %ecx
	shlq	$48, %rcx
	shlq	$40, %rbp
	shlq	$32, %rax
	orq	%rsi, %rax
	orq	%rcx, %rax
	orq	%rbp, %rax
	popq	%rbx
	popq	%rbp
	retq
                                        # -- End function

Compared to the byteorder version which just treats the floats as bytes.

playground::LLVector3::to_le_bytes_byteorder: # @playground::LLVector3::to_le_bytes_byteorder
# %bb.0:
	movl	8(%rdi), %edx
	movq	(%rdi), %rax
	retq
                                        # -- End function

I'm guessing LLVM couldn't see what we were trying to achieve among all the low-level byte operations.

2 Likes

Interesting.