Mehdi Akiki
Rust Failure Atlas / FFI and targets

RFA-603 · Case file with fixtures · Case 575 of 694 · Compiler evidence

Rust repr(transparent) Needs One Representation Field

Transparent layout delegates ABI to one unambiguous field. Store type-only metadata with PhantomData or choose a normal representation for runtime state.

Reviewed
Rust
Rust 1.98.1, edition 2024
Targets
all Rust targets
Profiles
dev, release, test

Direct answer

What this Rust failure means

Why it happens
A generic value intended only as a type marker was stored at runtime, leaving no single unambiguous field whose layout the wrapper delegates.
First discriminating check
Use PhantomData with reviewed variance when metadata is type-only, or remove transparency when the second value is real runtime state.

repr(transparent) promises that a wrapper has the same layout and ABI as one underlying field. That field must be unambiguous. The failing fixture stores f32 and a generic U; because U might also occupy space or require alignment, rustc emits E0690.

Generic parameters can become real runtime storage

The type name U might suggest a unit marker, but an unconstrained caller may instantiate it with String, u64, or another non-zero-sized type. Rust checks the generic definition for every permitted instantiation, not only the zero-sized example I had in mind.

The E0690 explanation states that transparent structs may have at most one field with non-trivial size or alignment. Extra zero-sized fields are permitted under the documented conditions.

If I truly need to store a runtime unit value, then the wrapper has two pieces of runtime state and should not claim transparent representation.

The repair stores type information without a value

The repaired fixture replaces unit: U with PhantomData<U>. PhantomData is zero-sized but tells the compiler that the type logically acts as if it owns or uses U for variance, auto traits, and drop checking.

The size assertion shows the demonstrated instantiation matches f32 size. For FFI I would also assert alignment and test the calling convention with the foreign compiler. Size equality alone does not prove ABI compatibility.

The standard PhantomData documentation warns that exact interactions can matter. I choose the marker form according to ownership and variance, not only to make E0690 disappear.

Transparent is an ABI promise, not an optimisation hint

The Reference defines transparent representation. It is useful for newtypes passed across FFI or APIs needing the inner representation while adding Rust type safety.

The attribute does not automatically make every conversion safe. The wrapper and inner type must have compatible validity for the operation. A constrained newtype may intentionally reject some inner values, so constructing it from arbitrary foreign bits still needs validation.

Transparent layout also does not mean stable field access or automatic trait forwarding. The wrapper remains a distinct Rust type and can control constructors, methods, and operators.

Zero-sized fields still affect type semantics

Although PhantomData<U> adds no runtime bytes, it can affect whether the wrapper is Send or Sync, how lifetimes vary, and what drop checker assumptions apply. A raw-pointer-shaped marker and an ownership-shaped marker may produce different properties.

I write compile-time trait assertions for the intended auto traits and negative compile tests for forbidden transfers when the wrapper guards a resource. Layout tests alone would miss these semantic effects.

Other zero-sized marker fields can be present, but a generic field is not guaranteed zero-sized merely because current uses happen to choose unit structs. Bounds do not generally provide a stable “always ZST” promise for arbitrary types.

Runtime metadata may deserve an ordinary struct

If measurements need a runtime unit code, calibration source, or uncertainty, I remove transparent representation and model those fields honestly. Serialisation can define a wire schema separately.

Conversely, a type-level unit such as Measurement<Meters> can prevent mixing metres and seconds without storing a unit per value. PhantomData supports this zero-cost distinction while transparent ABI can remain appropriate where proven.

I benchmark before claiming zero cost in the whole program. Monomorphisation, conversions, and validation may still affect code size or execution even when value layout is unchanged.

My E0690 checklist

  • Which field is intended to provide the transparent runtime representation?
  • Can any other field have non-zero size or non-trivial alignment?
  • Is a generic marker actually stored, or should it be PhantomData?
  • What ownership, variance, and auto-trait meaning should the marker carry?
  • Does the use case require transparent ABI or only a convenient newtype?
  • Are size, alignment, and foreign-call behaviour tested on all targets?
  • Do wrapper validity rules make arbitrary inner-bit conversion unsafe?
  • If metadata is runtime data, should the type use an ordinary representation?

The core principle is that transparent representation must point to one clear runtime field. I use type-only markers when the distinction is compile-time, and I abandon transparency when the domain truly contains additional runtime state.