0
votes

Here is my code:

use std::mem::{size_of, transmute};

#[derive(Debug)]
#[repr(C)]
struct Header { // some file header
    magic:      u32,
    data1_len:  u32,
    data2_len:  u32,
}
fn main() {

    let buffer: Vec<u8> = vec![1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0]; // whatever 

    let header: Header = {
        unsafe { transmute(buffer[0..(size_of::<Header>())]) }
    };
}

but when compiling I get this error:

error[E0277]: the size for values of type `[u8]` cannot be known at compilation time
   --> main.rs:15:22
    |
15  |         unsafe { transmute(buffer[0..(size_of::<Header>())]) }
    |                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time

Actually my type is sized, I am telling the precise range of bytes that my type occupies. Why does the compiler complain? It should derive the size of my type by itself. How do I tell the compiler that my size for transmute is the size of the the Header struct?

1

1 Answers

2
votes

Being Sized is a property of the type, not of any particular value. [u8], and [T] in general is unsized. You can fallibly try to convert to a sized type, however.

let data: [u8; size_of::<Header>()] = buffer[0..size_of::<Header>()].try_into().unwrap();

try_into usually returns a result, but since we know the size is correct, we can simply unwrap it. (playground)

// The trait `TryInto` isn't in the prelude,
// so we have to import it to use its methods.
use std::convert::TryInto;
use std::mem::{size_of, transmute};

#[derive(Debug)]
#[repr(C)]
struct Header {
    // some file header
    magic: u32,
    data1_len: u32,
    data2_len: u32,
}
fn main() {
    let buffer: Vec<u8> = vec![
        1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
        1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
    ]; // whatever

    let header: Header = {
        let data: [u8; size_of::<Header>()] = buffer[0..size_of::<Header>()].try_into().unwrap();
        unsafe { transmute(data) }
    };
}

Note: this step only works because [T; N] implements Copy if T implements Copy. We're copying the data out of the vector. If this isn't what you want, you'll need to remove the data from the vector using Vec::splice or similar. They can't both own that data.

You might also consider just getting a reference to a Header, which should actually work here without copying anything or modifying the vector. I'd avoid the wild unsafety of transmute and use the (slightly less unsafe) casting of raw pointers.

Unfortunately, the obvious thing to do here doesn't work since the alignment of a Header is 4 bytes, but the alignment of [u8] is only one byte. Pointer casting from &[u8] to &Header may cause an alignment fault. The reason that wasn't a problem with [u8; _] to Header was that transmute takes the array by value and returns a properly aligned value. See pull request #38670.

One way around this is to make Header repr(C, packed), which forces its alignment to be to one byte. This might make things a little slower in general (but remember: benchmark first!). I'm fairly certain this causes more problems than it's worth due to unsoundness around references to packed structs, so be careful with this. (playground)

use std::mem::size_of;

// We can't derive anything unless we also derive Copy
// one of the downsides of this approach
#[repr(C, packed)]
struct Header {
    // some file header
    magic: u32,
    data1_len: u32,
    data2_len: u32,
}
fn main() {
    let buffer: Vec<u8> = vec![
        1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
        1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
    ]; // whatever

    let header: &Header = unsafe { &*(buffer[0..size_of::<Header>()].as_ptr() as *const Header) };
}