Skip to main content

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[];
}
Structs Have No Methods

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.

  • extensible lets other structs name this one after extends.
  • abstract withholds construction: the type may be extended but never built by a struct literal. It only means something alongside extensible.
  • message marks 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 Marks the Interface

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 Contact is not a Radar.
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 = b labels the assertion a; assert a == b compares a and b.
  • 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 try turns it into none.
  • 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                                       // => 360

Errors​

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-instantiation

An 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
}
  • tuple — the same grouping without names on the parts.
  • newtype — a single field and its assertions, as a distinct type.
  • literals — writing a struct value, and updating one.