3

Rust sadly cannot produce a fixed size array [u8; 16] with a fixed size slicing operator s[0..16]. It'll throw errors like "expected array of 16 elements, found slice".

I've some KDFs that output several keys in wrapper structs like

pub struct LeafKey([u8; 16]);
pub struct MessageKey([u8; 32]);

fn kdfLeaf(...) -> (MessageKey,LeafKey) {
    // let mut r: [u8; 32+16];
    let mut r: (MessageKey, LeafKey);
    debug_assert_eq!(mem::size_of_val(&r), 384/8);
    let mut sha = Sha3::sha3_384();
    sha.input(...);

    // sha.result(r);
    sha.result( 
      unsafe { mem::transmute::<&mut (MessageKey, LeafKey),&mut [u8;32+16]>(&r) } 
    );
    sha.reset();

    // (MessageKey(r[0..31]), LeafKey(r[32..47]))
    r
}

Is there a safer way to do this? We know mem::transmute will refuse to compile if the types do not have the same size, but that only checks that pointers have the same size here, so I added that debug_assert.

In fact, I'm not terribly worried about extra copies though since I'm running SHA3 here, but afaik rust offers no ergonomic way to copy amongst byte arrays.

Can I avoid writing (MessageKey, LeafKey) three times here? Is there a type alias for the return type of the current function? Is it safe to use _ in the mem::transmute given that I want the code to refuse to compile if the sizes do not match? Yes, I know I could make a type alias, but that seems silly.

As an aside, there is a longer discussion of s[0..16] not having type [u8; 16] here

Reid Rankin
  • 1,078
  • 8
  • 26
Jeff Burdges
  • 4,204
  • 23
  • 46
  • 1
    Oops. I need to take a reference to the result of `unsafe`, but you get the idea : I do not want to `mem::transmute` a pointer. – Jeff Burdges Oct 26 '16 at 02:23
  • You should [edit] your own question instead of commenting to yourself. – Shepmaster Oct 26 '16 at 02:24
  • In fact, I must `mem:transmute` the reference since I need to pass the reference to `sha.result()`, but maybe I can find a safer way to do this. – Jeff Burdges Oct 26 '16 at 16:36

2 Answers2

3

There's the copy_from_slice method.

fn main() {
    use std::default::Default;

    // Using 16+8 because Default isn't implemented
    // for [u8; 32+16] due to type explosion unfortunateness
    let b: [u8; 24] = Default::default();
    let mut c: [u8; 16] = Default::default();
    let mut d: [u8; 8] = Default::default();

    c.copy_from_slice(&b[..16])
    d.copy_from_slice(&b[16..16+8]);
}

Note, unfortunately copy_from_slice throws a runtime error if the slices are not the same length, so make sure you thoroughly test this yourself, or use the lengths of the other arrays to guard.

Unfortunately, c.copy_from_slice(&b[..c.len()]) doesn't work because Rust thinks c is borrowed both immutably and mutably at the same time.

Linear
  • 21,074
  • 4
  • 59
  • 70
  • Yup, that's way more ergonomic than anything I'd seen. Thanks! I'll probably keep my `unsafe` for now, but if it starts to bug me than I'll look into doing a workaround with this. – Jeff Burdges Oct 26 '16 at 08:47
  • Just found a nice helper function `clone_into_array` in http://stackoverflow.com/a/37679442/667457 that lets me use almost the idiomatic commented out lines. – Jeff Burdges Oct 26 '16 at 09:04
0

I marked the accepted answer as best since it's safe, and led me to the clone_into_array answer here, but..

Another idea that improves the safety is to make a version of mem::transmute for references that checks the sizes of the referenced types, as opposed to just the pointers. It might look like :

#[inline]
unsafe fn transmute_ptr_mut<A,B>(v: &mut A) -> &mut B {
    debug_assert_eq!(core::mem::size_of(A),core::mem::size_of(B));
    core::mem::transmute::<&mut A,&mut B>(v)
}

I have raised an issue on the arrayref crate to discuss this, as arrayref might be a reasonable crate for it to live in.

Update : We've a new "best answer" by the arrayref crate developer :

let (a,b) = array_refs![&r,32,16];
(MessageKey(*a), LeafKey(*b))
Linear
  • 21,074
  • 4
  • 59
  • 70
Jeff Burdges
  • 4,204
  • 23
  • 46