I have a struct of two u64's, and the code that tries ref_from_bytes, crashes, even though alignment is 8, and the slice is a multiple of 8. Even if I try a slice multiple of 16 (336..352 instead of 328..344), it still shows the same error message.
What am I doing wrong?
/*
[dependencies]
zerocopy = {version = "0.8", features=["std", "derive"]}
*/
use zerocopy::{FromBytes, Immutable, KnownLayout};
#[derive(Debug, Clone, KnownLayout, FromBytes, Immutable)]
struct MyStruct {
first: u64,
second: u64,
}
fn main() {
let data = [0; 500];
println!("remainder: {}", 328 % 16);
println!("alingment: {}", align_of::<MyStruct>());
let val = MyStruct::ref_from_bytes(&data[328..328+16]).unwrap();
println!("ok, val {val:?}");
}
Outputs:
remainder: 8
alingment: 8
thread 'main' (28) panicked at src/main.rs:19:57:
called `Result::unwrap()` on an `Err` value: Alignment(AlignmentError)
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Notice that by default if you don't choose integer type, Rust assumes i32, which has alignment of 4 bytes. That makes data to have type [i32; 500] which also has alignment of 4. If your intention was for data to be an array of u64, then you can simply write [0u64; 500].
EDIT. I see now that ref_from_bytes expects &[u8]. This is harder problem, as it requires finding a correct place in slice where element is aligned to 8 bytes . You can use align_offset method on *const T to check that. For example:
#[derive(Debug, Clone, KnownLayout, FromBytes, Immutable)]
struct MyStruct {
first: u64,
second: u64,
}
impl MyStruct {
fn from_slice(bytes: &[u8]) -> Option<&MyStruct> {
const ALIGNMENT: usize = align_of::<MyStruct>();
const SIZE: usize = size_of::<MyStruct>();
let offset = bytes.as_ptr().align_offset(ALIGNMENT);
if offset + SIZE > bytes.len() {
return None;
}
let bytes = &bytes[offset..offset+SIZE];
MyStruct::ref_from_bytes(bytes).ok()
}
}
fn main() {
let data = [0; 500];
println!("remainder: {}", 328 % 16);
println!("alingment: {}", align_of::<MyStruct>());
let val = MyStruct::from_slice(&data[328..]).unwrap();
println!("ok, val {val:?}");
}
the index/offset is multiple of 8 doesn't mean the calcuated address is a multiple of 8. it only means the address is congruent to the start address modulo 8.
if the start address of the slice is not properly aligned, then address of the element at offset 8 will not be either.
the solution, as mentioned by others, is to either call align_to() on the storage of bytes, which requires you to allocate a larger buffer, or to wrap the slice into a newtype with sufficient alignment.
note, the #[repr(align(...))] attribute requires a literal number, it is not possible to give it other values, even if it is a compile time const such as std::mem::align_of::<T>().
a common trick is to use a zero sized type as a prefix the bytes, something like:
One additional side note. Maybe it won't matter for you, but maybe it does. What you are doing is reinterpreting bytes as u64. This will give different results on machines with different endianess. If data is only used as local scratch space, or you are transmuting it to MyStruct back-and-forth without inspecting raw data, then it probably doesn't matter. But if you are using data as storage for MyStructs, or you are reading it from a network, then you may be very much surprised, when different machine produces different results.
Maybe you're looking for the from_(be|le|ne)_bytes family of methods: u64 - Rust
#[derive(Debug)]
struct MyStruct {
first: u64,
second: u64,
}
fn main() {
let data = [0; 500];
let offset = 328;
let first = u64::from_be_bytes(*data[offset..offset+8].as_array().unwrap());
let second = u64::from_be_bytes(*data[offset+8..offset+16].as_array().unwrap());
let val = MyStruct { first, second };
println!("val: {val:?}");
}
Alignment is a property of an address/pointer. Index into array offsets the base address of the array. It doesn't make sense to assert that 328 is divisible by 8, you should assert that data.as_ptr().addr() + 328 is divisible by 328.
But of course it is silly, what you actually have to do is to understand rhe advice from the thread, as well as how it all works. Align the buffer on the type level or use paddings / align_to.