The Rust Engineering Handbook / Chapter 70
Representation, Unions, Bit Operations, and Transmutation
Separate layout, validity, provenance, and ABI obligations, then prefer explicit conversions over representation reinterpretation.
u8 and bool occupy one byte on the targets Rust supports. That does not make this a conversion:
let flag: bool = unsafe { std::mem::transmute(input_byte) };
If input_byte is 2, the source is a valid u8 and the destination is not a valid bool. Equal size proved only that the compiler could move the same number of bytes. It proved nothing about which bit patterns denote values, and creating an invalid value is undefined behavior even if the program never branches on it.
The correct boundary is ordinary validation:
let flag = match input_byte {
0 => false,
1 => true,
other => return Err(InvalidFlag(other)),
};
That short example contains the durable rule for this chapter: reinterpretation is justified only when the target value’s full representation contract has been proved; size equality is one small premise, never the proof. Most production code should avoid making that proof by using explicit numeric, byte, pointer, and enum conversions whose APIs state the intended semantics.
Five questions hidden by “these bytes mean X”
Representation work becomes reviewable when it is split into independent questions:
- Layout: what are the size, alignment, field order, and padding of the source and destination?
- Validity: is the exact source bit pattern a valid value of the destination type?
- Access: are the bytes initialized, properly aligned, within a live allocation, and accessed under compatible aliasing rules?
- Meaning: what endianness, discriminant mapping, numeric interpretation, or wire-format rule gives the bytes semantics?
- Stability: is that interpretation guaranteed across compiler versions, targets, optimization, and the foreign ABI?
A transmute review that records only size_of::<S>() == size_of::<D>() has answered part of question one. repr(C) may make some layout facts suitable for a named C ABI, but it does not validate arbitrary bytes, erase padding, establish pointer provenance, or define a portable serialization format.

The set diagram is deliberately asymmetric. Every bool representation is also some u8 bit pattern, but not every u8 value is a valid bool. For [u8; 4] and u32, every bit pattern is valid on both sides, yet the numeric result still needs an endian choice. Validity is necessary, not sufficient for meaning.
Representation attributes solve specific problems
Rust’s default representation permits the compiler to choose layouts consistent with language guarantees. Do not infer a stable field order or padding scheme for a default-layout struct from one build.
#[repr(C)] requests a C-compatible representation for structs, unions, and enums according to Rust’s documented rules. It is appropriate at a C ABI boundary when paired with the target platform’s ABI and compatible foreign declarations. It does not promise identical layout to an arbitrary C compiler configuration on every platform, nor does it make the type a stable disk protocol.
#[repr(transparent)] gives a one-field wrapper the representation and ABI of its non-zero-sized field, subject to the attribute’s restrictions. It is useful for typed handles and newtypes at an ABI boundary. The semantic type remains distinct: a transparent UserId(u64) should still validate domain rules in its constructor.
Integer representations on fieldless enums fix the discriminant’s primitive layout, but they do not declare every primitive value a valid enum. Given:
#[repr(u8)]
enum FrameKind {
Data = 1,
Ack = 2,
}
the byte 3 is not a FrameKind. The fixture implements TryFrom<u8> with an explicit match. This remains correct if the input is hostile, documents the accepted domain, and provides an error path.
Packed representation reduces or removes padding but can make fields unaligned. Creating a reference to an unaligned field is invalid even if hardware supports unaligned loads. Use raw-pointer operations designed for unaligned access when a packed external layout is unavoidable, and avoid taking an intermediate reference. Packed Rust structs are rarely the best wire decoder: byte-slice parsing with explicit offsets, lengths, and endian conversion usually states the protocol more clearly.
Alignment representations increase alignment; they do not initialize padding or imply cache-line isolation on every machine. If the purpose is false-sharing control, measure the deployed targets and document the performance assumption separately from the language layout fact.
Byte conversion should name byte order
Integers provide from_be_bytes, from_le_bytes, from_ne_bytes, and matching to_*_bytes methods. These operations are safe because integer bit patterns are valid and the method makes byte order explicit:
pub fn decode_header(bytes: [u8; 4]) -> u32 {
u32::from_be_bytes(bytes)
}
pub fn encode_header(value: u32) -> [u8; 4] {
value.to_be_bytes()
}
Network and persistent formats should normally choose big- or little-endian explicitly. Native endian is suitable only for process-local or platform-coupled representations whose portability limit is intentional and documented. A test that round-trips to_ne_bytes and from_ne_bytes on one host does not demonstrate cross-endian compatibility.
Floating-point types likewise have byte and bit conversion APIs. f32::from_bits accepts all u32 bit patterns as floating-point representations, including NaNs, but application invariants may reject NaN, infinity, negative zero, or non-canonical NaN payloads. Language validity and domain validity are different gates.
For slices, a byte view raises more questions. A &[T] may contain padding, and padding bytes need not be initialized in the way required for arbitrary byte reads. Even when T has no padding, exposing native memory gives a platform-dependent byte order and may reveal fields that were not intended for serialization. Prefer explicit field encoding. A type intended for generic byte casting needs a much stronger, well-documented contract than “plain old data,” including valid bit patterns, no uninitialized padding, stable layout, and alignment handling.
Padding is not data
Consider a C-layout record containing a u8 followed by a u32. Alignment commonly inserts bytes between the fields. Those bytes are not logical state. They may be uninitialized, may change across assignments or compiler transformations, and are not suitable inputs to hashing, equality, signing, or serialization.
This makes three tempting operations wrong:
- hashing the raw memory of a struct can hash padding and produce nondeterministic or information-leaking results;
- comparing raw bytes can report inequality for logically equal values;
- writing raw struct memory to disk can leak process data and ties the format to target layout.
Hash fields through their Hash implementations. Compare fields through Eq or a deliberate semantic comparison. Serialize each field through a specified format. If an external ABI requires a fully initialized C record, initialize fields and any required reserved bytes according to that ABI rather than assuming a Rust zeroing shortcut is valid for every member type.
mem::zeroed::<T>() is not a universal padding initializer. It attempts to create a typed T; zero must already be a valid representation for T. References, NonZero integers, many enums, and function pointers do not admit an all-zero value. When the job is to obtain zeroed untyped storage for later initialization, use an appropriate MaybeUninit operation and do not assume a T exists until all validity requirements are met.
Unions store bytes, not an automatic active-field tag
A Rust union overlays fields. Writing a field is safe; reading a union field is unsafe because the program must prove that the stored bytes can be interpreted as that field’s type. Unlike a C-style tagged-union protocol, the union itself does not remember which variant is active.
The lab uses a deliberately narrow example:
#[repr(C)]
union WordBytes {
word: u32,
bytes: [u8; 4],
}
Both fields cover four initialized bytes, and every bit pattern is valid for u32 and [u8; 4]. Reading the other field is therefore valid as a representation operation. The numeric meaning is native endian, so the safe wrapper names itself NativeWord and explicitly warns that it is not a wire decoder.
This argument does not generalize to union U { byte: u8, flag: bool }. Writing byte = 2 and reading flag would produce an invalid bool. Nor does a union make reference or pointer reinterpretation safe; provenance, alignment, aliasing, lifetime, and pointee validity remain.
If a program needs to know which interpretation is semantically active, store a tag outside the union and keep both private. Every constructor must initialize a field compatible with the tag; every accessor must match the tag; every transition must update storage and tag in an unwind-safe order; and Drop must destroy exactly the active field when fields need destruction. Often a Rust enum already implements that state machine more safely and compactly enough. Use a union when layout or FFI constraints justify its added proof burden, not merely to imitate a lower-level style.
ManuallyDrop fields can make union members legal when they otherwise require destruction, but ManuallyDrop does not perform active-field tracking. It transfers the obligation to the wrapper. A public union containing resource owners is an especially sharp escape hatch because safe field writes may overwrite a live owner without dropping it.
Transmute is a bundle of obligations
std::mem::transmute::<Src, Dst>(src) is a by-value bitwise move subject to equal size and destination validity. It is not “cast with extra power.” The operation must account for:
- equal size at the types actually instantiated;
- destination alignment where later accesses require it;
- initialized bits required by the destination;
- a destination-valid bit pattern;
- preservation of pointer provenance and lifetime where pointer or reference fields are involved;
- no ownership duplication or forgotten destructor obligation;
- layout and ABI assumptions stable enough for the intended use;
- padding not being treated as stable logical data.
The compiler checks size equality for a concrete transmute, but that does not relieve the other obligations. Generic transmutes are often rejected because sizes cannot be established for all instantiations; working around that rejection with pointer reads does not solve validity.
References are particularly dangerous targets. Turning an integer into &T must somehow prove a live allocation, provenance, alignment, initialization, aliasing, and a valid lifetime. A numeric address supplies none of those facts. Changing &T to a longer-lived &'static T does not extend the allocation. Turning &T into &mut T violates exclusivity unless an independent model justifies it, and ordinary shared references do not.
Lifetime transmutes are a warning that the type relationship has been lost. Prefer designing the owner and borrow together, storing an index or handle, or using a pinned/self-referential abstraction whose constructor and projection methods establish the relation. An unsafe lifetime extension may be sound inside a tightly controlled owner, but its proof spans every way the owner moves, mutates, and drops. It should not become a general utility function.
A replacement table for common transmutes
Most intended operations have narrower stable APIs:
| Intent | Prefer | Why it is stronger |
|---|---|---|
| bytes to integer | from_be_bytes, from_le_bytes, from_ne_bytes |
names byte order; all integer patterns valid |
| integer to bytes | to_*_bytes |
copies logical value into explicit bytes |
| primitive numeric conversion | From, TryFrom, or as with documented semantics |
expresses range/truncation policy |
| byte to enum | TryFrom<u8> or explicit match |
validates the discriminant set |
T pointer to U pointer |
raw-pointer .cast() |
changes pointer type without inventing a reference |
| pointer mutability weakening | .cast_const() or coercion |
states direction; does not create ownership |
| slice element reinterpretation | parse/copy explicitly, or a narrowly audited casting abstraction | handles alignment, length, validity, and lifetime |
| bit-preserving float/integer view | to_bits / from_bits |
documented representation operation |
| rotate bits | rotate_left / rotate_right |
handles distance modulo width correctly |
| choose a function | enum/match or ordinary function item coercion | avoids data/function-pointer representation assumptions |
| ownership conversion | consuming constructor, ManuallyDrop plus audited read only when necessary |
makes destructor transfer explicit |
as is not automatically preferable. Its numeric semantics can truncate, wrap, or saturate depending on source and destination categories, and it can expose pointer addresses. Use it when those documented semantics are the intended policy. For a narrowing input boundary, TryFrom is usually clearer. For an intentional low-bit extraction, a mask plus a narrowing cast may be clearer still.
Bit operations need domain contracts
Bitwise code is safe Rust but can still be incorrect, nonportable, or security-sensitive. Specify:
- signed or unsigned interpretation;
- bit numbering and byte order;
- behavior for shift distances at or beyond the type width;
- whether overflow is wrapping, checked, saturating, or rejected;
- which bits are reserved and whether unknown bits are preserved;
- canonical encoding requirements;
- constant-time requirements, if any.
Rust’s integer rotate methods make modulo-width rotation explicit. Shift operators can panic or otherwise differ with overflow checks and optimization context when the right operand is out of range; do not let build profile choose protocol behavior. Validate the distance or use checked/wrapping methods appropriate to the contract.
Bitflags decoded from untrusted input should define unknown-bit policy. Rejecting unknown bits protects a closed protocol; retaining them can support forward-compatible round trips; masking silently may erase information and make signatures or audits ambiguous. The choice is a versioning decision, not just an operator.
Signed right shift is arithmetic for signed integers, which may be wrong for a logical bitfield. Convert through an unsigned type only after documenting the desired width and value interpretation. Avoid shifts on inferred integer literals whose type changes under surrounding code.
Function pointers are not data pointers
Rust function items coerce to function-pointer types with a particular Rust or foreign ABI. A safe function pointer and an unsafe function pointer carry different call obligations. extern "C" fn and Rust fn do not have interchangeable ABIs merely because their machine addresses happen to have the same width on one target.
Do not transmute a data pointer or integer into a function pointer based on size. Some architectures represent them differently; an address may not denote executable code; control-flow integrity or pointer authentication may add constraints. Dynamic symbol lookup is inherently platform- and loader-specific. Keep it behind an API that returns the exact expected function-pointer type only after checking symbol presence and documenting library lifetime, calling convention, unwind policy, thread rules, and version compatibility.
When choosing among known functions in Rust, store the function pointer directly or use an enum and match. The latter can make configuration serializable without pretending code addresses are stable identifiers.
Serialization and hashing must ignore object layout
An in-memory representation is optimized for a compiler and target. A serialized representation is a compatibility contract. They should coincide only through an explicit, maintained decision.
A robust binary format specifies field order, integer widths, endianness, tag values, length units, canonical forms, unknown-field behavior, and version evolution. It validates before constructing types with restricted validity. It does not dump enum discriminants or struct memory unless a separately defined ABI makes that exactly the required protocol.
Hash logical fields rather than object bytes. For security hashes or signatures, define a canonical serialization first; otherwise multiple byte encodings of one logical value can undermine comparisons or signatures. Avoid including padding, addresses, randomized map order, native endian, or compiler-chosen discriminants.
Zero-copy parsing does not remove these obligations. It adds alignment, lifetime, provenance, and aliasing constraints to format validity. If a borrowed view has a restricted-validity field, validate its bytes before exposing a typed reference. Copying four or eight bytes into an integer is frequently cheaper than maintaining a broad unsafe zero-copy contract, especially once I/O and cache behavior dominate.
Platform ABI review is a matrix, not an assertion
At an external boundary, record at least:
| Dimension | Review question |
|---|---|
| target | Which architecture, OS, and environment triples are supported? |
| data model | What are integer widths, pointer width, alignment, and endian? |
| calling convention | Which extern ABI applies, and does the target support it? |
| aggregate layout | Are both sides using compatible field, packing, and alignment rules? |
| enum/boolean representation | Are accepted values and widths specified by the foreign contract? |
| unwind | May an exception or panic cross the boundary? |
| ownership | Which allocator creates and destroys buffers or handles? |
| toolchain | Which compiler flags, headers, bindings, and versions were verified? |
Compile-time size and alignment assertions catch drift on tested targets. Generated or hand-checked headers catch declaration mismatches. ABI test programs should exchange representative values in both directions. CI should cover every claimed target family. These are evidence for the matrix, not a guarantee for untested toolchains.
Rust’s repr(C) is part of this plan, not the whole plan. A C-compatible struct containing a pointer still needs lifetime and ownership rules. A callback still needs thread, reentrancy, and unwind rules. Chapters 72–73 build those boundary protocols.
Distinguish numeric casts from representation casts
Rust’s as operator covers several conversions, but they do not all mean “reuse these bits.” Integer-to-integer casts follow documented truncation, sign extension, or zero extension rules based on source and destination widths. Floating-to-integer casts use defined saturating behavior for out-of-range values in current Rust semantics. Integer-to-floating conversion rounds according to floating-point representation. These are value conversions, even when the generated machine instruction is cheap.
That distinction matters in review. Converting -1_i8 as u8 yields 255; it does not make an invalid value, but it may violate an application range. Converting 300_u16 as u8 yields the low eight bits, which can be correct for a deliberate mask and wrong for a length. u8::try_from(300_u16) makes rejection visible. Use a named policy:
TryFromfor fallible range enforcement;Fromfor lossless, infallible conversions available by contract;wrapping_*for modular arithmetic;checked_*for explicit failure;saturating_*for clamping;to_bits/from_bitsfor documented floating representation;- masks and shifts for specified bitfield extraction.
Pointer casts change pointer type or expose an address; they do not dereference and do not validate the target pointee. Casting *const u8 to *const Header can be a useful intermediate operation, but reading still requires Header alignment, initialized bytes, a valid Header bit pattern, suitable provenance, in-bounds range, and compatible aliasing. Keeping the result raw preserves the opportunity to check those facts before creating &Header.
Raw pointer casts also do not transfer ownership. Converting a pointer obtained from Box<T> into another pointer type does not authorize Box::from_raw with a different layout. Deallocation must use the original allocation contract: correct type/layout, allocator, and exactly one owning reconstruction. A pointer-sized newtype can improve type separation without changing these rules.
Niche optimization is not a serialization rule
Rust may use invalid or otherwise unavailable bit patterns of a field as enum discriminants. The familiar example is that Option<&T> can often use the null representation for None, making it pointer-sized. Similar niche optimizations can occur for nonzero integers and other types.
Use documented size guarantees where the standard library or Reference actually provides them, but do not generalize an observed niche layout to arbitrary enums. Adding a variant, changing a field type, changing representation attributes, or compiling for another target can change layout. Even when Option<NonZeroU32> is known to be the same size as u32, arbitrary u32 bytes should be decoded through the semantic mapping: zero means None; nonzero goes through NonZeroU32::new.
Niches illustrate why “all bytes are data” is wrong. Some patterns mean no value of the field type; the compiler can use those patterns precisely because valid safe values never contain them. Manufacturing an invalid field through a raw write can corrupt the enclosing enum’s interpretation and let later safe code assume an impossible state.
If compact layout is an external requirement, define an explicit encoded type made from byte arrays and integer tags, then convert to the domain type. This separates stable encoding from optimized in-memory layout. It also gives unknown discriminants and future variants an error or preservation path.
Representation changes cross API boundaries
Changing repr, field order, field type, or enum variants can affect more than FFI. It can invalidate persisted memory maps, shared-memory protocols, kernel interfaces, device descriptors, cryptographic transcripts, dynamically loaded plugins, and unsafe downstream code. A private default-layout struct gives the implementation freedom to change; a public field layout or raw-parts API can accidentally spend that freedom.
SemVer alone does not describe ABI stability. Rust’s native ABI is not generally a stable cross-compiler plugin ABI. Two crates rebuilt together can use typed Rust interfaces safely, while independently shipped binaries need an explicitly stable boundary—often C ABI, a serialized protocol, or a process boundary. repr(C) stabilizes relevant layout relative to a named C contract but does not stabilize symbol names, generics, trait objects, unwinding, or allocator ownership.
For memory-mapped files, never map bytes and immediately cast the base to &Header solely because a C-layout struct matches today’s format. Validate file length and offsets; handle alignment; define byte order; validate restricted fields; avoid references if the mapping can be mutated or invalidated; and account for format version. Parsing into owned values is the robust baseline. A borrowed view is an optimization with an unsafe lifetime and validity case.
For shared memory, normal references and atomics add concurrency requirements. A C-layout u32 field does not become safely concurrent because its representation is known. Both processes must agree on atomic width, alignment, ordering, initialization, lifetime, and crash recovery. Rust atomic types have platform availability constraints and should not be replaced with plain integers plus volatile access.
Volatile is not a representation escape hatch
Volatile reads and writes are intended for externally observable memory access such as some memory-mapped I/O. They affect whether accesses may be elided or combined; they do not make an invalid T valid, make an unaligned address aligned, establish provenance, provide atomic synchronization, or solve data races.
A device register API should usually wrap raw addresses and expose typed operations whose masks, access widths, read/write side effects, and ordering requirements follow the device specification. Some registers are read-only, write-one-to-clear, or unsafe to read. Mapping the register block as an ordinary Rust struct and borrowing all fields can perform forbidden accesses or imply reference guarantees that device memory does not satisfy.
Use integer storage types accepted by the hardware, volatile pointer operations where required, and explicit bitfield methods. Keep address derivation and target support in the unsafe kernel. If memory barriers or architecture instructions are required, document them independently from compiler-level volatile semantics.
Performance arguments need evidence
Explicit conversion is often optimized to the same instructions as reinterpretation. u32::from_ne_bytes can compile to a move; big-endian decoding can become a byte swap on a little-endian target; a checked enum match can become a compact range test. Unsafe is not an optimization directive.
Measure before replacing copying parsers with borrowed typed views. Small fixed-size copies are friendly to registers, avoid alignment branches, and detach the result from input lifetime. A zero-copy view may save bandwidth for large payloads, but it can increase cache misses, retain a large backing allocation, complicate ownership, and force repeated validation. The cost model includes bytes copied, validation passes, branch behavior, lifetime retention, alignment fallbacks, and review surface.
For hot bit operations, inspect generated code only after preserving a source-level contract. Prefer portable intrinsics such as count_ones, leading_zeros, byte swaps, rotations, and checked arithmetic. The compiler can map them to target instructions. Hand-written transmutes or architecture-specific assembly require target gating, fallback behavior, register and clobber correctness, and benchmarks on deployed CPUs.
Optimization can also change the threat model. Table lookups indexed by secrets, data-dependent branches, and ordinary equality may leak timing even when representation handling is memory-safe. Constant-time behavior is not guaranteed by writing branchless-looking Rust. Use reviewed primitives and verify emitted behavior for supported targets when the security contract requires it.
Incident patterns worth recognizing
Representation failures tend to surface far from the unsafe line:
- An invalid enum discriminant reaches a
matchwhose optimizer assumes all declared variants, producing behavior that looks impossible in source. - Raw-struct hashing changes between debug and release because padding differs.
- A native-endian cache file works in one fleet and fails after migration to another architecture.
- A packed field reference faults only on a stricter-alignment target.
- A plugin built with a different compiler disagrees about an unstabilized Rust ABI.
- A function-pointer transmute passes tests on a conventional desktop but fails under pointer authentication or control-flow enforcement.
- A zeroed foreign record contains a Rust field for which zero is invalid.
- A zero-copy view outlives or aliases mutation of its backing buffer.
During incident response, preserve the exact binary, compiler, target, flags, input bytes, and foreign declarations. Reproduce with a minimal boundary and inspect values before typed construction where possible. Sanitizers or Miri may expose some access violations, but an invalid-value bug can already give the optimizer license to transform the program. Fix the API so invalid construction is impossible or checked; do not merely add a branch after the invalid value exists.
Representation incidents are also compatibility incidents. Determine whether persisted data, network peers, plugins, or foreign callers have already adopted the accidental layout. Recovery may require a versioned decoder or dual-format migration rather than changing the struct and redeploying all at once.
A final proof decomposition
When unsafe conversion is truly necessary, write the safety case in this order:
- Name the semantic result, not the mechanism.
- Fix the supported source and destination types, targets, and versions.
- Prove size, alignment, layout, and initialization.
- Prove the exact bytes are destination-valid.
- Prove pointer provenance, aliasing, and lifetime when applicable.
- Prove ownership and destruction happen exactly once.
- Separate native representation from wire or ABI meaning.
- Encapsulate the operation behind the narrowest safe API possible.
- Test positive, negative, cross-endian, and target-specific cases.
- Record what the tests and tools cannot prove.
If one step cannot be written concretely, the transmute is not ready. Usually the failure reveals a better design: parse bytes, validate a tag, preserve a raw pointer until access, transfer ownership through a consuming constructor, or move interoperability to a stable process boundary.
Exercise: remove the transmutes
Audit a module containing these operations:
u8to a two-variant#[repr(u8)]enum.[u8; 4]to a protocolu32.u32to[u8; 4]for persistence.&[u8]to&[Header]by pointer cast.- an integer to
bool. - a numeric selector to a function pointer.
- raw struct bytes used as a cache key.
For each, write the intended semantics before changing code. Replace enum and boolean conversions with validation; byte conversions with endian-specific methods; the slice cast with explicit parsing unless a measured requirement justifies a fully audited view; function selection with a typed table or enum; and raw-memory hashing with field-wise or canonical-format hashing.
If any unsafe reinterpretation remains, submit an obligation record covering layout, validity, initialization, alignment, provenance, aliasing, lifetime, ownership, padding, ABI, and version scope. Add negative tests for invalid discriminants, malformed lengths, misalignment, unknown bits, and cross-endian fixtures. State which properties tests cannot prove.
Then compare the result with unsafe-abstractions-lab: FrameKind::try_from rejects value 3; header encoding uses big endian; native union interpretation is explicitly process-local; rotation uses rotate_left; and layout assertions are labeled observations for tested targets rather than a portable wire contract.
Representation review card
- What exact semantic conversion is intended?
- Is there a safe API that names it directly?
- Are source and destination sizes, alignments, and padding known?
- Is this exact bit pattern valid for the destination?
- Are all read bytes initialized?
- For pointers or references, where do provenance, aliasing, and lifetime come from?
- Does ownership move exactly once, with the correct destructor?
- Is byte order explicit?
- Are enum discriminants and reserved bits validated?
- Is object layout being mistaken for serialization or hashing format?
- Which targets, ABIs, compiler versions, and foreign toolchains support the claim?
- Can a union or transmute be replaced by an enum, match, byte conversion, typed cast, or consuming constructor?
Representation code is not inherently wrong, but it is unusually dense with independent contracts. Narrow APIs make those contracts visible. A checked enum conversion states a validity set. from_be_bytes states byte order. A raw-pointer cast avoids prematurely creating a reference. A safe union wrapper states which cross-field reads are valid and why. The remaining unsafe code becomes smaller and more stable—and its obligations are ready to be composed with unsafe traits, destruction, unwinding, and concurrency.
Sources and version notes
Core examples target Rust 2024, and the companion fixture declares Rust 1.85.0 as its MSRV. Primary references are the current Rust 1.97.0 documentation for type layout, unions, behavior considered undefined, casts, function types, transmute, integer byte conversions, and pointer casts. Platform ABI claims must be rechecked against the supported target documentation and foreign toolchain; observed size assertions in the lab are not promoted to universal guarantees.
Continue reading
Full table of contents