Documentation/Chapters

Records, alternatives and missing values

On this page

Use a record when several values describe one thing. Use an enum when a value is one of a fixed set of names. Use a closed choice when different alternatives carry different kinds of data. Use an optional when an otherwise ordinary value may be absent. These representations let the compiler check your assumptions.

Records

TYPE Customer
    Name AS STRING
    Visits AS INTEGER = 0
    Notes AS LIST OF STRING = []
END TYPE
customer = Customer { Name="Ada" }
copy = customer
copy.Visits += 1
Append(BYREF copy.Notes, "Called today")
PRINT customer.Visits
PRINT LEN(customer.Notes)
PRINT copy.Visits

Named construction supplies fields in any written order. Every field needs an explicit value or a supported declared default. Explicit values evaluate in written order, then omitted defaults in declaration order. Empty collection defaults create independent collections for each record. Positional construction, such as Customer("Ada"), follows field declaration order and may use trailing defaults. Named function arguments are not a second spelling for record fields.

Ordinary copyable records compare their fields recursively. If a field owns a file or another resource, the containing record becomes noncopyable. A record with a cleanup hook also becomes a resource; chapter 10 explains that change.

Enums

ENUM Priority
    Low
    Normal
    High = 10
END ENUM
priority = Priority.High
PRINT EnumValue(priority)
PRINT priority = Priority.High
PRINT Priority(10) = priority

Members are qualified by the enum name. Values start at zero and increment unless explicitly assigned. Enums are distinct static types, not aliases for INTEGER. EnumValue extracts the number; construction from INTEGER checks membership and can fail with NumericError. Duplicate values, aliases and implicit numeric conversion are not provided.

Optionals

DIM nickname AS OPTIONAL OF STRING = NONE
IF LET text = nickname THEN
    PRINT text
ELSE
    PRINT "No nickname"
END IF
nickname = SOME("")
IF LET text = nickname THEN
    PRINT "Present, length="; LEN(text)
END IF

NONE means absence. SOME wraps a present value, even if that value is empty, zero or FALSE. IF LET checks presence and introduces a branch-local independent binding for a copyable payload. A borrowed stable optional supplies a read-only copy; a fresh result or TAKE optional supplies a writable owner. You cannot access an absent payload. Nested optionals distinguish outer absence from SOME(NONE), which is useful when a dictionary contains an explicitly absent value or a task successfully returns NONE.

Resources have different IF LET ownership rules: a named optional loans its payload; a fresh or explicitly moved optional can provide an owned payload. Chapter 10 shows both.

Choose a fallback only when absent

optional ?? fallback unwraps one optional layer. A present value wins even if it is zero, FALSE, empty text or an empty collection. Only NONE evaluates the fallback:

PRINT SOME(0) ?? DefaultNumber()
DIM absent AS OPTIONAL OF INTEGER = NONE
PRINT absent ?? DefaultNumber()
PRINT "[" + (SOME("") ?? "missing") + "]"
FUNCTION DefaultNumber() AS INTEGER
    PRINT "Calculating fallback"
    RETURN 99
END FUNCTION

Output is 0, then Calculating fallback and 99, then empty brackets. This differs from dictionary Get with a fallback, whose ordinary argument evaluates eagerly. A fallback can fail through normal checked contracts; its contract is checked even when a particular execution skips it.

The operator associates to the right and binds below OR, AND, comparisons and arithmetic. Use explicit parentheses when unwrapping nested optionals, such as (outer ?? SOME(7)) ?? 9. A present outer SOME(NONE) is still present at the first step. A fresh resource optional can donate its payload; a named resource optional needs TAKE for ownership transfer. No implicit move or resource copy is introduced. Borrowed copyable payloads produce independent values when ownership is needed. See optional fallback and defaults for parameter rules.

Closed choices

TYPE Reply
    CASE Empty
    CASE Text AS STRING
    CASE Number AS INTEGER
END TYPE
reply = Reply { Text="Ready" }
SELECT TYPE reply
CASE Reply.Empty
    PRINT "No reply"
CASE Reply.Text AS text
    PRINT text
CASE Reply.Number AS number
    PRINT number
END SELECT

A choice holds exactly one declared alternative. SELECT TYPE checks the alternatives, with independent case-local copyable payload bindings. Cover all alternatives or provide the supported fallback form. Projecting a payload, such as reply.Text, returns an optional; projecting a payload-free alternative returns a Boolean. Alternative fields cannot be assigned directly: assign a complete new choice instead.

Records and choices may form finite recursive values through supported lists, dictionaries and optionals. A direct inline containment cycle is invalid. Recursive value trees do not imply general shared reference graphs or cyclic back-references.

Record extension and opaque types

TYPE Person
    Name AS STRING
END TYPE
TYPE Customer EXTENDS Person
    Visits AS INTEGER = 0
END TYPE
customer = Customer { Name="Ada", Visits=2 }
ShowName(customer)
base = Person(customer)
PRINT base.Name
SUB ShowName(person AS Person)
    PRINT person.Name
END SUB

EXTENDS reuses fields and permits checked base access. It does not introduce classes, virtual methods, downcasts or runtime subtype identity. Multiple bases are allowed when layouts have no collisions or repeated ancestors. A default base parameter borrows the actual value; Person(customer) explicitly constructs an independent base value. BYREF base access preserves the derived fields and cannot replace the complete base storage.

OPAQUE TYPE is a module boundary: clients can name and own values but cannot construct or extend the record or access hidden fields. Explicit PUBLIC fields are readable outside the defining module, including independently copying copyable fields. They do not grant external mutation, BYREF, SWAP or TAKE. Public field visibility is currently for ordinary opaque records; arbitrary member visibility on ordinary transparent records is not supplied. Chapter 12 shows the module pattern.

From the BASIL documentation. View source ↗

Find your way around BASIL

Type to search the manual.