struct
A named record of zero or more typed fields. Structs form single-inheritance hierarchies with
extends, and modifiers control whether one may be extended, built, or sent between components.
struct TrackReport {
track_id: uint32;
bearing_deg: float32;
range_m: float32;
}Fields
Each field is a name, a colon, a type, and a semicolon. Field order is the declaration order, and it is the order a struct literal and the wire form both use.
- A struct may declare zero fields.
- A field may be typed with the struct being declared, so a tree is a struct that holds an array of itself.
- Field names are unique within a struct, and unique against every inherited field.
struct Empty {}
struct Waypoint {
name: string;
legs: Waypoint[];
}A struct declares fields and assertions and nothing else — no method, no constructor body, no
visibility modifier. Behavior lives in a function, a transform, or a protocol, so a struct is only
the data.
Modifiers
These modifiers may precede struct, in this order: abstract, extensible, message.
extensiblelets other structs name this one afterextends.abstractwithholds construction: the type may be extended but never built by a struct literal. It only means something alongsideextensible.messagemarks a struct as something components send each other in a protocol.
extensible struct Contact {
contact_id: uint32;
}
abstract extensible struct Sensor {
sensor_id: uint32;
}
message struct SlewCommand {
sensor_id: uint32;
bearing_deg: float32;
}message picks out the types that cross a component boundary: a protocol sends these, a transform
maps between them, and a plain struct may still be a field inside one. Choosing what to mark is an
ontological question: which types are things the system is made of, and which are only how a component
does its work. That answer belongs in the declaration, not in whatever the protocols happen to send.
Inheritance
extends names one parent, so the hierarchy is a tree rather than a lattice. A struct holds every
field its ancestors declare, ahead of its own.
- The parent must be a struct, and it must be declared
extensible. - Inherited fields cannot be redeclared, retyped, or reordered.
- A value of a struct is accepted anywhere one of its ancestors is expected. The reverse is not
true: a
Contactis not aRadar.
extensible struct Contact {
contact_id: uint32;
}
struct Radar extends Contact {
scan_period_ms: uint32;
}
const fixed: Contact = Radar { contact_id = 4; scan_period_ms = 2000; };Assertions
A run of assert statements may close a struct body. They state what has to be true of a value of
this type, and they are checked every time one is constructed.
struct SearchSector {
start_deg: uint16;
end_deg: uint16;
assert ordered = start_deg < end_deg;
assert end_deg <= 360;
}- Assertions come after every field. A field written below an assertion is a parse error.
- The name in
assert ordered = …is a label, reported when the invariant fails.assert a = blabels the assertiona;assert a == bcomparesaandb. - Every own field is in scope, whatever the declaration order. Inherited fields are not.
- Each condition has type
bit. - A failing assertion is a catchable failure, so
tryturns it intonone. - A struct-update re-checks the invariants after applying the overrides, so an update cannot reach a value the equivalent literal is rejected for.
struct SearchSector {
start_deg: uint16;
end_deg: uint16;
assert ordered = start_deg < end_deg;
}
const forward: SearchSector = SearchSector { start_deg = 350; end_deg = 360; };
try(SearchSector { start_deg = 90; end_deg = 10; }) // => none
try(SearchSector { forward with end_deg = 10; }) // => none
forward.end_deg // => 360Errors
Extending a struct that is not extensible
A parent has to opt in with extensible before anything may extend it.
struct Contact {
contact_id: uint32;
}
struct Radar extends Contact { // error: extends-non-extensible
scan_period_ms: uint32;
}Extending something that is not a struct
extends names a struct. An alias, a newtype, or an enum in that position has no fields to inherit.
type ContactId = uint32;
struct Radar extends ContactId { // error: extends-non-struct
scan_period_ms: uint32;
}Marking a struct abstract without extensible
An abstract struct cannot be constructed, so unless it is also extensible no value of it can
ever exist.
abstract struct Sensor { // error: abstract-requires-extensible
sensor_id: uint32;
}Declaring the same field twice
struct TrackReport {
track_id: uint32;
track_id: uint32; // error: duplicate-field-name
}Redeclaring an inherited field
A child holds its ancestors' fields already, so naming one again would give the struct two fields with one name.
extensible struct Contact {
contact_id: uint32;
}
struct Radar extends Contact {
contact_id: uint32; // error: inherited-field-collision
}Inheritance that forms a cycle
extensible struct Contact extends Radar { // error: cyclic-inheritance
contact_id: uint32;
}
extensible struct Radar extends Contact { // error: cyclic-inheritance
scan_period_ms: uint32;
}Constructing an abstract struct
An abstract struct is a place to put shared fields. Construct one of its descendants instead.
abstract extensible struct Sensor {
sensor_id: uint32;
}
const s: Sensor = Sensor { sensor_id = 1; }; // error: abstract-struct-instantiationAn assertion whose condition is not bit
struct SearchSector {
start_deg: uint16;
assert start_deg; // error: assert-condition-type
}Naming an inherited field in an assertion
An assertion sees only the fields its own declaration lists.
extensible struct Contact {
contact_id: uint32;
}
struct Radar extends Contact {
scan_period_ms: uint32;
assert contact_id > 0; // error: unresolved-reference
}