We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Unsafe and Soundness step 6 of 24
`&raw const` / `&raw mut`: a pointer without a reference
#[repr(C, packed)]
struct Rec { tag: u8, val: u32, flag: u8 }
pub fn packed_fields(bytes: Vec<u8>) -> Vec<i64>
Reinterpret bytes as a back-to-back sequence of six-byte Rec records —
one u8 tag, a little-endian u32, one u8 flag — and return
[tag, val, flag] for each complete record, flattened. A trailing partial
record is dropped. An empty input gives an empty output.
The starter does not compile, and its error, E0793, is the entire point of this item.
&x as *const T is a trap
Every older tutorial writes this:
let p = &x as *const T;
Read what it actually does. &x creates a reference first. Then the as
cast decays that reference into a pointer. For the fraction of an instruction
in between, a real &T exists — and a real &T carries real promises the
compiler is entitled to act on: the place is aligned, it is initialised, it
holds a valid T, and nothing else may mutate it while the reference lives.
Usually those promises are true and nothing goes wrong. Three cases where they are not:
-
a
repr(packed)field, which may be at any alignment — that is a misaligned reference, and creating one is undefined behaviour even if it is never dereferenced; -
an uninitialised place inside a
MaybeUninit, where the bytes are not a validTyet (item 17.23); -
a
static mut, where another reference may already be live.
&raw const x and &raw mut x never materialise a reference at all. They
produce a raw pointer to the place directly, and they demand nothing of it —
not alignment, not initialisation, not exclusivity. That is why the operator
exists, and it is why it is correctness, not style.
::: question rustc’s error on &(*p).val for a packed struct says “creating a
misaligned reference is undefined behavior (even if that reference is never
dereferenced)”. Why “even if”?
Because a reference’s guarantees attach the moment it is produced, not the
moment it is used.
A &u32 is, at the language level, a value with a validity invariant:
non-null, aligned to 4, pointing at an initialised u32. Producing a value
that violates its type’s validity invariant is instant UB — assigning it to a
place is enough. Item 17.12 is entirely about this distinction, and it is the
single idea that makes MaybeUninit and &raw look obvious rather than
ceremonial.
So there is no window in which you “have” a misaligned reference and have not yet done anything wrong. The wrongness is the having. :::
ptr::addr_of! is on its way out
Before the &raw operator was stabilised (Rust 1.82), the same job was done
by the macros ptr::addr_of! and ptr::addr_of_mut!. You will meet them in
existing code and in older blog posts.
The std documentation now marks them soft-deprecated in favour of &raw,
and the precise write permissions addr_of! grants remain an open question.
Learn &raw const / &raw mut; read addr_of! when you meet it; do not write
it.
A second on-by-default lint you will meet
rustc’s dangerous_implicit_autorefs is deny-by-default and it catches a
genuinely sneaky pattern:
let s = unsafe { &mut (*cell.get())[lo..hi] };
error: implicit autoref creates a reference to the dereference of a raw pointer
Indexing autorefs. So does any method call on (*p). (*p).len() silently
creates a &String you did not write, with every guarantee a reference
carries. If you meant to stay in raw-pointer land, say so — the lint’s own
suggestion is to write (&*p).len() if the reference really is what you
wanted, or to reach for a raw-pointer method if it is not.
The shape of the solution
Rec is #[repr(C, packed)]. Packed means no padding and alignment 1, so
size_of::<Rec>() is exactly 6 and any byte address is a legal *const Rec.
That makes bytes.as_ptr().cast::<Rec>() sound, and .add(i) steps six bytes
at a time.
Note the C in repr(C, packed). Clippy’s repr_packed_without_abi fires on
a bare #[repr(packed)]:
warning: unqualified `#[repr(packed)]` defaults to `#[repr(Rust, packed)]`,
which has no stable ABI
If you are laying a struct over bytes that came from somewhere else, you want
the field order and offsets nailed down, and only repr(C) promises that.
Inside each record, val sits at byte offset 1 — so on every single record it
is misaligned for a u32. Reach it with &raw const (*p).val and read it with
ptr::read_unaligned, which compiles to whatever unaligned load the target
supports. A plain ptr::read on that address is undefined behaviour; on x86
it will even give the right answer, which is the worst possible outcome.
::: question tag is a u8 at offset 0 and flag is a u8 at offset 5.
u8 has alignment 1, so no address can be misaligned for it. Do those fields
need &raw const too?
Strictly, no — but write it anyway, and here is the better reason.
rustc’s packed-field check is about alignment, and a u8 field can never be
misaligned, so &(*p).tag is accepted. What is not guaranteed by the
pointer’s type is that the byte is initialised, and &raw const is the
operator that expresses “I want the address, I am promising nothing about what
is there”.
The practical argument is uniformity. A reader scanning this loop should see
one idiom, not two, and should not have to recompute the alignment of each
field to know whether the author was being careful or lucky. The moment
somebody widens tag from u8 to u16, the two-idiom version acquires a
silent UB bug and the one-idiom version does not.
:::
What the harness cannot check
Swap read_unaligned for read and every test here still passes on x86-64
and on aarch64. Unaligned loads are supported in hardware on both; the
undefined behaviour is at the language level, which means the optimiser is
permitted to assume it never happens and may act on that assumption in a way
that only shows up after some unrelated change. Nothing in this grader can see
it. That is the honest state of affairs for the whole track — see item 17.14
for what Miri can and cannot add.
Remember the grade is compile + tests + clippy -D warnings.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.