Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Value representations

Classes, tuples, and enum payloads keep fields inline in their owner’s storage. Native slots contain ordinary value bits plus any required inline payload bytes. Moves and copies relocate private addresses into the destination; addresses never extend the lifetime of departed frames. Immutable metadata describes field layout, ownership, relocation, equality, and formatting. Concrete layout facts are cached.

Runtime record traversal advances a running slot offset, including each field’s inline payload and any class cleanup state word. A complete pass takes one width lookup per field, without allocating an offset table. Hashing, equality, ordering, formatting, copying, relocation, and retain/release use this traversal. Copy failure cleanup walks the completed prefix backwards with a decreasing offset. This bounds offset work per pass; nested payload work and repeated visits while queued destruction resumes remain separate costs. Strings, the most common heap field, are freed during the pass and cause no resumption.

A class with exactly one field and no __del__ is represented as that field: same slot, same bytes, no record and no internal address. Constructing it, reading or moving its field, copying it, and releasing it are the field’s own operations; Wrap(file) costs nothing beyond file. Its runtime descriptor carries the field’s layout under the class’s name, so formatting still prints Wrap(x=1) and columnar lists still see one column. A one-field class with __del__ needs the cleanup state word and keeps the record layout, as do classes with two or more fields. The flat class layout proposal records why the record holds an address and the layout contract C interop depends on.

A field whose type is a class with a record (after looking through one-field wrappers) is stored flat when its owner is a class record, a tuple, or an enum variant with two or more fields: its record bytes sit at a fixed offset in the owner’s record, with no slot word of its own. Reading, borrowing, matching or writing owner.inner.x adds constant offsets to the owner’s record address; moving the owner copies its bytes with no address fixes for flat fields, so a tuple or variant of such classes moves as plain bytes. An all-zero flat record is an empty field, so moving a field out zeroes its bytes and releasing an empty field does nothing. The rule has these exceptions, which keep the slot word followed by the record:

  • top-level class values (locals, list elements, boxes, captures), because runtime operations and sum payloads receive one 128-bit value;
  • the payload of a single-field variant, such as Some(point) or Ok(point), which is that one 128-bit value with the sum’s tags stacked in its high word: the record follows it, and moving the sum fixes only that one address, as moving a bare class value does;
  • field-less classes without __del__, which have no record bytes to place.

A reference to a class value names the 16 bytes before its record whether or not a slot word is stored there: readers use address + 16 without loading the word, writers copy the record bytes, and writing an empty value zeroes them. A __del__ hook receives such a reference and must not read the word. The runtime release walk clears a finished flat record so a resumed walk skips it.

Compiled code avoids the runtime walk when the type makes it unnecessary. A payload with no internal addresses, such as a range, a flat record, or a closure environment without inline captures, is copied as plain bytes; a sum whose payloads are all such is copied by testing its tag. An all-zero value is an empty ownership slot, such as a moved-from local, and its release makes no runtime call. These paths keep high-frequency helpers, such as error mappers called per byte, out of runtime operation calls.

Record copies and clears of up to 192 bytes are emitted inline as 16-byte vector loads and stores rather than memmove and memset calls. A local moved straight into a call argument, with only operand pushes between the move and the call, is passed at its own address instead of a fresh copy: the callee copies its arguments on entry, so the caller’s storage is not read again. Inlined constructors, generator producers and portable SIMD operations keep argument addresses, so moves into them still copy.

A class with a record also has a plan in its runtime descriptor: the word offsets of the strings it owns, including those of flat class fields, whether those strings are all it owns, and whether its bytes hold any address into themselves. When the strings are all it owns (no __del__ at any depth, no other managed field), the runtime releases the record by freeing those strings directly instead of walking its fields; freeing a string runs no user code, so the order is unobservable. A record holding no internal addresses moves without relocation: only its owner’s value word changes. Collection rows use both, and a list whose row class owns nothing skips its per-row release entirely.

References use two native words: an address and packed inline-sum tag-offset information. They point into typed local, frame, field, or collection storage; source loans determine when those locations remain usable. Function values are code addresses without capture environments. Returned-reference analysis caches precise field-origin summaries when available and otherwise uses conservative owner projections.

Scalar references read and write only the scalar’s declared bytes, whether they address a packed list element, a local slot, or a projected enum payload. Slot padding is not part of the scalar value; runtime equality, hashing and formatting ignore it. Projected writes leave ancestor tags unchanged.

Generator resumption reserves space for the complete inline yield layout after the runtime result word. The shared runtime-call scratch area grows for larger records and nested payloads; yielding a record does not impose a fixed-size limit.

Lists

Dense lists pack primitive numeric and boolean elements into 1-, 2-, 4- or 8-byte rows with natural alignment. Other rows contain a 16-byte value slot and its inline payload, with no unused dictionary value slot. The runtime keeps a 16-byte-aligned buffer, rounding allocation sizes up to whole words; its exposed length counts initialized bytes. Borrowed scalar indexing uses the concrete element width to calculate row counts and addresses. General borrowed iteration obtains the runtime row stride. Native strided FFI views use that same stride, and signed reads extend their declared bytes without a post-call repair pass. List storage costs describe the remaining space used by sum and record elements.

Strings

A str is one 64-bit word. Heap and literal pointers address a 32-byte prefix: a 16-byte managed header, byte length, and scalar count, followed by UTF-8 bytes without a terminator. Literal storage is immortal and eight-byte aligned.

A set low bit marks an inline string. Its low byte stores length << 1 | 1; the remaining seven bytes store UTF-8. These values own no heap allocation and retain/release ignores them. Byte equality, hashing, and printing handle both representations identically. Source-visible text behavior belongs in text operations.

A &substr is two words, like a &[u8]: the address of its first UTF-8 byte and its byte count. It has no prefix, so it is not a &str. A view of an inline string addresses the bytes in the borrowed string’s slot, which its loan keeps in place.

Standard sums

Option and Result use a 128-bit value: a 64-bit terminal payload and a 64-bit path of binary tags, outermost first. Nested sums add bits without boxing, bounded by the 64-level type limit. The payload is scalar bits, an owned heap handle, or a private address of inline data. No numeric sentinel or null pointer is reserved as a source value. LoadError uses three terminal tag bits for seven variants.

Scalar/pointer sums use 16-byte addressable slots. Inline payload bytes follow the value word; slots grow only when the payload requires it. Native results use caller-owned storage, and relocation preserves sum tags. Runtime aggregate helpers receive aligned slot pointers instead of relying on a C u128 calling convention. A one-word operand is stored as its word and a zero word, so no two-word zero stays live across the call (issue #757). These layouts are private and are not the public FFI ABI.

Ranges

Ranges contain 32 bytes of bounds, step, and length. Stored ranges use 48-byte slots: ordinary value storage plus inline range data. Collection relocation and frame/result moves repair embedded addresses. Range creation and return need no separate owner allocation; see ranges.