Accessing packed field in Rust and Compiler recommendation

48 Views Asked by At

Consider following simple rust program

#[repr(packed)]
pub struct TwuMsg {
    pub id: u64,
}

fn main() {
    let msg = TwuMsg { id: 1 };
    println!("msg.id {}", msg.id);
}

It fails to compile. rustc suggests following help

= help: copy the field contents to a local variable, or replace the reference with a raw pointer and use `read_unaligned`/`write_unaligned` (loads and stores via `*p` must be properly aligned even when using raw pointers)

But if I change main according to note, It works fine.

fn main() {
    let msg = TwuMsg { id: 1 };
    // isn't msg.id is also accessing unaligned field too? Why it's considered safe?
    let id = msg.id;
    println!("msg.id {}", id);
}

I tried to get answers from different sources. They claimed that rust compiler ensures the safety. Then my counter would be

why rustc can't ensure safety for direct reference to field if it can for copy operation?

If you can explain the diff in terms of CPU and instructions generated that would be deeply appreciated.

[UPDATE]

I've gone through official reference already at https://doc.rust-lang.org/reference/behavior-considered-undefined.html#places-based-on-misaligned-pointers but I'm more interested to know about the motive behind this design decision. Usually if data is unaligned CPU will spend additional cycle for the read. Is performance penalty is the only reason behind considering this a UB?

2

There are 2 best solutions below

2
Tachibana Shin On

This is due to the memory allocation for rust's secure access system. First when you use #[repr(packed)] rust will sort the fields with minimal alignment leading to some fields that can never be aligned (this is due to the way memory is allocated Rust hardware does not determine it). unaligned access and field leads to uncomputable behavior (it's easy to hack like ios m8 bug)

in the first code you try to directly access msg.id of type u64 8byte from an address that may not be 8 byte this is dangerous behavior rustc will ban it

In the second code you are doing a read of let id = msg.id you are copying to id which is safe because it will copy the bytes from msg.id to id without caring about the alignment of the result is id aligned to 8 bytes because rust always ensures correct local variable alignment

important here rust ensures compile safe copying as it involves moving bytes but it cannot guarantee safe reference to fields as it involves unsafe memory access

Easy explanation [repr(packed)] rust will not provide any padding . for example if id only has 6 bytes then rust will give it a whole row of bytes because there is nothing 2 bytes to put in that place so id can instead be accessed Its 6 bytes' have the ability to access another 2 bytes' next to it in the memory bar. If you don't want to use let id = msg.id you can #[repr(C)] this mode will add padding to fill the memory to prevent unauthorized access.

0
Chayim Friedman On

Rust has the invariant that references are always aligned to the type's required alignment. Violating this invariant is Undefined Behavior.

Packed structs have unaligned fields, so you cannot take reference to them, only raw pointers or copy or direct assignment. println!() implicitly takes references to its arguments, so it cannot be used with packed fields.

Note that you don't need an additional variable; using curly braces will force a copy:

println!("msg.id {}", { msg.id });

Playground.

Also note that debating about CPU support for unaligned access is not correct here. Even if the CPU does not support unaligned store or load, Rust will still make them work (for example, by copying bytes). References could be made to work in the same way. The only reason is that unaligned references are UB. Why this is the case can be attributed (at least partially) to micro-architectures penalizing unaligned access.