Clause
← All documentation

Native records

Decision of plan 11 step 31A (2026-10-07). It scopes the OTP 29 record forms beyond ordinary tuple records and fixes their representation, operations and errors. Step 31B implemented the runtime cells and CLAUSE_record_v1; 31C compiles local native records, 31D qualified and imported forms with -export_record/-import_record, 31E anonymous forms. Tuple records are in terms.

OTP 29 marks native records experimental. Facts below come from the pinned maint-29 sources (erl_lint, erl_expand_records, v3_core, beam_core_to_ssa, erts/emulator/beam/erl_record.c, utils.c, erl_printf_term.c) and live probes on OTP 29.1.1.

Scope

All three forms beyond tuple records are selected. They share one representation, so they need no separate representation decision:

FormSyntaxStep
Native record, local-record #r{...}., #r{...}, X#r.f, X#r{...}, patterns, is_record/1,2,331C
Qualified and imported#m:r{...}, X#m:r.f, X#m:r{...}, patterns; -export_record, -import_record31D
Inferred (anonymous)X#_.f, X#_{...}, pattern #_{...}31E

Not selected: the records reflection module (a library, with step 39's library work), term_to_binary (RECORD_EXT) and hot code upgrade (D01). Calls to records:* stay unknown module diagnostics.

Representation

Operations

M is the current module. All errors have class error; badrecord payloads are the offending value except failed external construction.

OperationAcceptsFailure
Local construction #r{...}Always (definition is local)Unknown field or missing value are compile errors
External construction #m:r{...} (also imported #r{...}, also #M:r)m:r defined in the batch and exported{badrecord, {m, r}}; then {badfield, {{m, r}, F}}, {novalue, {{m, r}, F}} (badfield wins)
Local access X#r.fNative record named r (module and export flag not checked, as OTP's runtime){badrecord, X}; field missing {badfield, {{Mod, r}, f}}
External access X#m:r.fModule m, name r, exported{badrecord, X}; {badfield, ...}
Anonymous access X#_.fAny native record (export not checked, as OTP's runtime){badrecord, X}; {badfield, ...}
Local update X#r{...}Module M, name r{badrecord, X}; unknown field {badfield, ...}
External update X#m:r{...}Exported, module m, name rsame
Anonymous update X#_{...}Exported or module Msame
Local pattern #r{...}Module M, name r, every listed field presentno match
External pattern #m:r{...}Module m, name r; exported when a field is listedno match
Anonymous pattern #_{...}Any native record; exported or module M when a field is listedno match

Compile-time rules

Messages follow erl_lint:

Printing and order

Differences kept

OTPClause
display field order follows atom indexesDefinition order
External construction of a module that is not loaded fails until it is loadedEvery batch module is loaded at startup; a module outside the batch always fails
Compiler may fold == between records to =:= (#r{a=1} == #r{a=1.0} is false)Runtime == (true)
Unknown fields in access/update/patterns, own-module qualified construction without values, unused or header-defined records: warningsNo warnings