Reading c-style structures from disk

@kornel, those seem like decent ideas, if I wasn't constrained by formats I cannot control. Luckily I can control what this will be used for (3 different file types/structures so far), and so "well if you use it with XYZ rust type, it'll break" isn't a large concern of mine. I'm decoding C-defined POD types, that's it. That it will break on some things you can pass in is OK for now. I have read the Rust Koans and so I know I'm violating the 3rd, but I will definitely be looking into bytemuck as others have suggested, so I can see if applying the POD trait there is appropriate to constrain this further so it can't be misused.

I have constrained the unsafe usage to a single line now, and I don't think I have any memory unsafety anymore, as long as T is a POD type, which I can't enforce with baseline Rust it seems.

// Reads in the specified struct from the specified path.  If there is not an exact match on size, returns an error
fn read_struct<T: Default, P: AsRef<Path>>(path: P) -> std::io::Result<Box<T>> {
    let path = path.as_ref();

    // Compare the type's size to that of the file we're reading from, returning on error
    let struct_size = ::std::mem::size_of::<T>();
    let num_bytes = fs::metadata(path)?.len() as usize;
    if struct_size != num_bytes {
        return Err(std::io::Error::new(
            std::io::ErrorKind::Other,
            format!(
                "Length of file did not match structure specified.  File length: {}, Expected: {}",
                num_bytes, struct_size
            ),
        ));
    }

    // Open the file to read from
    let mut reader = BufReader::new(File::open(path)?);
    // Initialize the memory we're going to write into
    let mut boxed_value = Box::new(T::default());
    // Get a pointer to write to directly from the file.  Pointer doesn't leak, as boxed_value still owns the memory
    let ptr_from_box: *mut T = &mut *boxed_value;
    // Turn the pointer we have into a slice so we can read into it from BufReader
    let buffer = unsafe { slice::from_raw_parts_mut(ptr_from_box as *mut u8, num_bytes)};
    // Do the actual read from disk into the memory that boxed_value allocated above.  If this goes wrong, boxed_value still owns the memory, and destructs it
    reader.read_exact(buffer)?;
    // Read went fine, so return the box, as it's been populated with the contents of the file
    Ok(boxed_value)
}

I agree that the Box::new_uninit() functionality is what I really want so I don't need to initialize it, but for stable right now I think what's above only has known limitations (TOCTOU, and POD dependence), and no actual flaws. But that's the whole point of posting this topic: to have those pointed out to me.