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
Implement packed_fields(bytes: Vec<u8>) -> Vec<i64>.
The input contains six-byte records with this layout:
| Byte offset | Field | Encoding |
|---|---|---|
| 0 | tag | u8 |
| 1 through 4 | val | little-endian u32 |
| 5 | flag | u8 |
Return each complete record as [tag, val, flag], joining the results
into one vector. Ignore a trailing partial record. Inputs shorter than
six bytes produce an empty vector.
For example, [1, 2, 3, 4, 5, 6] produces [1, 84148994, 6]:
the middle four bytes encode 0x05040302. Preserve the full unsigned
u32 range when converting to i64, including 4294967295.
This exercise practices raw field pointers. Use the supplied
#[repr(C, packed)] record, &raw const and read_unaligned to
read its fields. A safe byte-slice decoder is another reasonable design
for ordinary parsing code, but does not practice this technique.
Why a reference followed by a cast fails
&x as *const T first borrows x, then converts that reference to a raw
pointer. This is valid when x can be borrowed. It is unsuitable for a
packed field whose alignment is not guaranteed: the intermediate
reference must already satisfy the requirements of &T.
The starter's &(*p).val triggers E0793 for that reason. Casting such
a reference afterward would not fix it. Use &raw const (*p).val
to obtain the field's address directly, without borrowing the field.
References must meet their validity requirements when created, even if the next operation would discard them. A reference cannot be used as an intermediate step to reach unaligned or uninitialized storage.
Raw pointer creation still has requirements
&raw const produces a *const pointer; &raw mut produces a *mut
pointer. Neither operation itself reads the value at the resulting address.
They can therefore address storage that is not ready for a reference.
This does not make every operand valid. Computing a field address through a pointer still requires an in-bounds field projection. A pointer cast also does not establish that the destination is safe to read or write. Keep the allocation alive and respect the access permissions of its source.
Expressions that invoke overloaded indexing or dereferencing can create references while evaluating the operand. Likewise, a method with a borrowed receiver can introduce a reference through automatic borrowing. Not every method call does this: check its receiver type.
The dangerous_implicit_autorefs lint rejects certain implicit borrows
through raw pointers. An explicit borrow is appropriate only when you
can justify that reference's validity; it is not a general safety fix.
Older code may use std::ptr::addr_of! and addr_of_mut! for the
corresponding raw borrow operations. They are soft-deprecated in favor
of &raw const and &raw mut. The new spelling does not grant extra
access permissions.
Record layout and unaligned reads
For this record, repr(C) preserves declaration order, and packed lowers alignment to one. Its size is six bytes, with val at offset one. A bare repr(packed) uses Rust's default layout and does not give the same field-order guarantee; Clippy's repr_packed_without_abi flags that omission.
A field at offset one is not necessarily misaligned at every actual address. The important point is that u32 alignment is not guaranteed. Use read_unaligned so the read works in either case.
read_unaligned relaxes alignment only. The pointer must still cover a readable, initialized value. Here each complete record consists of bytes owned by the input vector, and all bit patterns are valid for u8 and u32. Keep that vector alive without reallocating or modifying its buffer.
The tag and flag fields have alignment one and could use ordinary reads. Using raw field pointers and read_unaligned for all three fields keeps the loop consistent. Changing a field's type would require reviewing the entire layout and its reads, not just retaining this syntax.
Byte order is separate from alignment
Reading a u32 from memory uses the machine's native byte order.
Apply u32::from_le to the loaded integer because the input format
specifies little-endian bytes. read_unaligned does not decode endianness.
Compute the number of records with bytes.len() / size_of::<Rec>().
This excludes the partial tail before any pointer access. For each record,
read the three fields, convert the middle value from little-endian, and
widen each value to i64 before appending it.
What passing tests means
Tests cover output values, empty input and incomplete records. They cannot prove that all unsafe accesses are valid. Replacing read_unaligned with read can introduce undefined behavior when alignment is insufficient, regardless of whether a particular machine appears to produce the answer.
Review the bounds, initialization, ownership and access assumptions in each safety comment. See the memory-model article for additional checking tools and their limits.
The submission must compile, match the expected output and pass Clippy with warnings denied. Besides fixing E0793, replace the starter's zero placeholders for tag and flag and handle the input's byte order.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.