Skip to content

CEP-0063: Representation Modes and C Layout

Draft

Draft proposal for post-V1 representation modes. V1 accepts only .catalyst layout; .c, .packed, C layout aliases, and compound C ABI expansion are deferred.

Summary

Catalyst should eventually support additional representation modes for structs, including C-compatible layout and packed layout. V1 keeps the source surface deliberately narrow: @repr(.catalyst) is accepted and redundant, while @repr(.c) and @repr(.packed) are rejected.

This proposal captures the deferred design pressure around representation modes so the V1 compiler can keep its layout implementation extensible without exposing incomplete layout promises.

Motivation

Systems code needs layout control for FFI, wire formats, binary protocols, memory-mapped data, and compact storage. The design needs more than an attribute spelling: it must define field ordering, padding, aggregate alignment, nested aggregate eligibility, target C ABI facts, reflection output, and diagnostics for unsupported combinations.

Accepting @repr(.c) or @repr(.packed) before those rules are precise would make source code appear safer and more portable than it is. V1 instead keeps deterministic .catalyst layout and leaves future representation modes to this proposal.

Proposed Direction

Representation modes should remain semantic, not provenance-only. If a representation mode is accepted, it must affect final layout and T.layout() deterministically.

Future Type.Repr may grow from the V1 shape:

const Repr = enum {
  catalyst,
}

to a richer shape such as:

const Repr = enum {
  catalyst,
  c,
  packed,
}

Possible future semantics:

  • .catalyst remains the default internal Catalyst layout.
  • .c requests selected-target C-compatible field layout for supported field types.
  • .packed requests deterministic packed field placement with explicit rules for unaligned field access.
  • T.layout() reports final computed layout for the selected target and representation mode.
  • representation-mode-specific unsupported forms are hard errors, not silent fallbacks.

C Layout Direction

@repr(.c) needs a target C ABI data-layout profile rather than ad hoc assumptions. That profile should include the selected C widths and alignments for integer aliases, floating types, object pointers, function pointers, size_t, ptrdiff_t, and maximum object alignment.

A future std.c namespace may provide target-selected aliases for trivially mapped C primitive types:

std.c.Bool
std.c.SChar
std.c.UChar
std.c.Short
std.c.UShort
std.c.Int
std.c.UInt
std.c.Long
std.c.ULong
std.c.LongLong
std.c.ULongLong
std.c.Float
std.c.Double
std.c.Size
std.c.PtrDiff
std.c.IntPtr
std.c.UIntPtr

These should be aliases to existing Catalyst primitive type identities, not distinct nominal wrapper types. ABI and layout validation should operate on resolved type identity and target facts, so equivalent user aliases remain accepted where the underlying type is accepted.

Non-trivially mapped C types should stay outside the first expansion unless separately designed:

  • plain C char;
  • long double;
  • wchar_t and C character-width families;
  • C enums;
  • C bit-fields;
  • C unions;
  • C _Complex;
  • C _Atomic;
  • C23 _BitInt;
  • variadic functions;
  • struct passing and return ABI details.

@repr(.c) field eligibility should be separate from C function ABI safety. A type can have a defined C-compatible memory layout without being accepted by value in an exported C function signature.

Packed Layout Direction

@repr(.packed) must define:

  • whether packing is recursive or only affects containing field placement;
  • how struct-level and field-level @align interact with packed layout;
  • whether by-value reads and writes of packed fields are guaranteed to work on strict-alignment targets;
  • whether taking the address of an under-aligned packed field is allowed, warned, or rejected;
  • how nested field paths compose for alignment diagnostics;
  • how T.layout().fields[i].align distinguishes storage placement alignment from a field type's normal required pointer alignment.

One likely direction is C-like sharp-tool behavior: by-value field access works through lowering, taking the address of a potentially under-aligned packed field is allowed but warns, and dereferencing an actually under-aligned typed pointer is illegal behavior on targets that do not support it. That direction must be accepted deliberately before .packed becomes source-valid.

Example

Invalid V1; possible future shape:

@repr(.c)
const Header = struct {
  tag: std.c.UInt
  len: std.c.Size
}

Invalid V1; possible future shape:

@repr(.packed)
const WireHeader = struct {
  tag: u8
  len: u32
}

Accepted V1 equivalent when no external representation promise is required:

@repr(.catalyst)
const Header = struct {
  tag: u32
  len: usize
}

V1 Compatibility

V1 accepts only .catalyst representation. @repr(.catalyst) is valid but redundant. @repr(.c) and @repr(.packed) are hard errors with diagnostics that point to this proposal.

V1 compiler implementations are encouraged to route layout through an internal representation-mode abstraction even though only one source representation is accepted. That keeps type finalization, layout reflection, SIR, and IR ready for future layout modes.