Skip to content

Tracking issue for safe_packed_borrows compatibility lint #46043

Closed
@arielb1

Description

@arielb1

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:

  1. remove the #[repr(packed)] attribute if it is not actually needed. A few crates had unnecessary #[repr(packed)] annotations - for example, tendril.
  2. 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
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    B-unstableBlocker: Implemented in the nightly compiler and unstable.C-future-incompatibilityCategory: Future-incompatibility lintsC-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions