Description
What is this warning about
Fields of structs with the #[repr(packed)]
attribute might be unaligned. Even if a field has a valid offset (e.g. 0) within the struct, the struct itself might be unaligned.
On the other hand, safe references (&T
and &mut T
) must always have valid alignment (even when they are just created) - not only will dereferencing these references cause segfaults on architectures such as Sparc, but the compiler is allowed to optimize based on that assumption (e.g. in a future version of rustc, it might use the "free" alignment bits to represent enum discriminants), so even just creating an unaligned reference is Undefined Behavior and might cause the program to behave in unexpected ways.
Therefore, borrowing a field in the interior of a packed structure with alignment other than 1 is unsafe - if the reference is not known to be aligned, the borrow must be done to an unsafe pointer.
Note that borrowing a field with alignment 1 is always safe.
For example, consider the common struct Unaligned
:
#[repr(packed)]
pub struct Unaligned<T>(pub T);
That struct can be placed inside a bigger struct at an unaligned offset:
pub struct Foo {
start: u8,
data: Unaligned<u32>,
}
In that case, a borrow of a field of the struct would be Undefined Behavior (UB), and therefore is made unsafe:
let x = Foo { start: 0, data: Unaligned(1) };
let y = &x.data.0; // UB, also future-compatibility warning
println!("{}", x.data.0); // this borrows `x.data.0`, so UB + future-compat warning
use(y);
This used to be left unchecked by the compiler - issue #27060.
How to fix this warning/error
The most common ways to fix this warnings are:
- remove the
#[repr(packed)]
attribute if it is not actually needed. A few crates had unnecessary#[repr(packed)]
annotations - for example, tendril. - copy the fields to a local first. When accessing a field of a packed struct directly without using a borrow, the compiler will make sure the access is done correctly even when the field is unaligned. The then-aligned local can then be freely used. For example:
let x = Foo { start: 0, data: Unaligned(1) }; let temp = x.data.0; println!("{}", temp); // works // or, to the same effect, using an `{x}` block: println!("{}", {x.data.0}); // works use(y);
In some cases, it might be necessary to borrow the fields as raw pointers and use read_unaligned
/write_unaligned
to access them, but I haven't encountered such a case.
Derives
One annoying case where this problem can appear is if a packed struct has builtin derives, e.g.
#[derive(PartialEq, ...)]
#[repr(packed)]
pub struct Unaligned<T>(pub T);
Which essentially expands to this:
impl<T: PartialEq> PartialEq for Unaligned<T> {
fn eq(&self, other: &Self) -> bool {
PartialEq::eq(&self.0, &other.0) // this is UB and unsafe
}
}
If your struct already derives Copy
and has no generics, the compiler will generate code that copies the fields to locals and will therefore work. Otherwise, you'll have to write the derive
implementations manually.
For example, you might want to add a T: Copy
bound and copy things out:
#[repr(packed)]
pub struct Unaligned<T: Copy>(pub T);
impl<T: Copy + PartialEq> PartialEq for Unaligned<T> {
fn eq(&self, other: &Self) -> bool {
({self.0}) == ({other.0}) // this copies fields to temps, and works
}
}