Skip to content

Records & objects

When several values belong together, such as the X and Y of a point or the name and balance of an account, d/BASIC gives you two ways to group them: a TYPE, which is a record you can copy, and a CLASS, which is an object with an identity of its own. This page explains both, shows how objects are constructed and freed, how one class builds on another, how properties work, and how to choose between a record and a class. Choosing the right one is easier than memorizing either syntax:

A TYPE is a value you can copy. A CLASS is an object with identity.

TYPE: a value record

TYPE Point
  X AS INTEGER
  Y AS INTEGER
END TYPE

DIM A AS Point, B AS Point
A.X = 3
A.Y = 4
B = A
B.X = 99

PRINT A.X   ' 3: B is an independent copy

A record is stored inline, right where its variable is. Assigning one record to another copies its fields. A record has no constructor, no destructor, no identity, no virtual methods and no reference count; all of those belong to classes, described below.

Use a record for coordinates, colors, headers, small messages, dates, and any other data that should travel as a self-contained value.

Record fields

A record's fields can hold numbers, fixed-width strings and other records, and nothing else. A field cannot be an array, a variable-length STRING, a CLASS reference, a handle to a host resource such as an open file, or a WEAK link (explained below). A record may not contain itself, directly or through another record; the compiler stops and says so.

TYPE NameCard
  Code AS STRING * 8
  Name AS STRING * 32
  Ranking AS INTEGER
END TYPE

STRING * n declares a fixed-width text field inside a record; n can be anything from 1 through 65,535. Assigning text longer than n cuts it down to n bytes; assigning text shorter than n pads it with spaces; and every stored byte counts. The rule is deliberately exact:

DIM Card AS NameCard
Card.Name = "Alex"

PRINT LEN(Card.Name)             ' 32
PRINT Card.Name = "Alex"         ' 0: the padded field is not four bytes
PRINT RTRIM$(Card.Name) = "Alex" ' -1

An ordinary variable-length STRING holds only the bytes it needs, behind a counted reference, which is why it cannot sit inside a record's fixed layout. When a model needs variable-length strings, or anything else that works by reference, use a CLASS. The fixed-string rule is useful when a record must be stored or sent as an exact image of bytes, but it is also a trap for newcomers, worth learning once.

Arrays of records are ordinary arrays of values:

DIM Route(0 TO 15) AS Point
Route(0).X = 12
Route(0).Y = 8

CLASS: identity and behavior

Here is the first object example, worth reading end to end:

CLASS Greeter
  PRIVATE:
    Name AS STRING
  PUBLIC:
    DECLARE CONSTRUCTOR (Initial AS STRING)
    DECLARE SUB SayHello ()
END CLASS

CONSTRUCTOR Greeter (Initial AS STRING)
  THIS.Name = Initial
END CONSTRUCTOR

SUB Greeter.SayHello ()
  PRINT "HELLO, "; THIS.Name; "!"
END SUB

DIM G AS Greeter
G = NEW Greeter("ADA")
G.SayHello
G = NOTHING

Read it in four passes:

  1. CLASS declares the shape of the object and which members callers may use.
  2. The bodies of members live at module level, outside the class, under qualified names such as Greeter.SayHello.
  3. NEW constructs one object and returns a reference to it.
  4. NOTHING releases the variable's reference to the object.

Declaring a class variable does not create an object. The variable starts out as NOTHING; only NEW runs a constructor.

Identity changes assignment

DIM A AS Greeter, B AS Greeter
A = NEW Greeter("MIRA")
B = A

A and B now refer to the same object. Change it through either name and the change shows through the other. Use A IS B to ask whether two names refer to the same object, and A IS NOTHING to ask whether a name is empty.

SET A = B is accepted for those used to older object-oriented BASIC dialects; plain A = B means the same thing when the destination is a class variable: it assigns the reference, not a copy.

Access sections

CLASS Account
  PRIVATE:
    Balance AS DECIMAL
  PROTECTED:
    Revision AS LONG
  PUBLIC:
    DECLARE FUNCTION CurrentBalance () AS DECIMAL
END CLASS
  • PUBLIC members can be used by any caller.
  • PROTECTED members can be used inside the class and inside classes derived from it.
  • PRIVATE members can be used only inside the class that declares them.

The compiler enforces these. They are part of the compiled class, not a convention your editor might or might not respect.

THIS

Inside a member body, THIS is the object the member was called on:

FUNCTION Account.CurrentBalance () AS DECIMAL
  CurrentBalance = THIS.Balance
END FUNCTION

Writing THIS.Balance rather than plain Balance makes it clear which state belongs to the object, especially when a parameter has a similar name.

Constructors and destructors

A constructor runs when an object is created; a destructor runs when it is freed. A class can declare several constructors that take different parameters, and one destructor that takes none:

CLASS Note
  PUBLIC:
    Text AS STRING
    DECLARE CONSTRUCTOR ()
    DECLARE CONSTRUCTOR (Initial AS STRING)
    DECLARE DESTRUCTOR
END CLASS

CONSTRUCTOR Note ()
  THIS.Text = "UNTITLED"
END CONSTRUCTOR

CONSTRUCTOR Note (Initial AS STRING)
  THIS.Text = Initial
END CONSTRUCTOR

DESTRUCTOR Note
  ' Release application-owned state here.
END DESTRUCTOR

d/BASIC frees objects by reference counting, and it does so at a predictable moment: when the last strong reference to an object is released, its destructor runs right then. There is no garbage collector that pauses the whole program.

Construction runs the base class first, then the derived class. Destruction runs the other way round: derived first, then base.

Inheritance and BASE

CLASS NamedThing
  PROTECTED:
    Name AS STRING
  PUBLIC:
    DECLARE CONSTRUCTOR (Value AS STRING)
    DECLARE VIRTUAL FUNCTION Description$ ()
END CLASS

CLASS ButtonThing EXTENDS NamedThing
  PUBLIC:
    DECLARE CONSTRUCTOR (Value AS STRING)
    DECLARE FUNCTION Description$ () OVERRIDE
END CLASS

CONSTRUCTOR NamedThing (Value AS STRING)
  THIS.Name = Value
END CONSTRUCTOR

CONSTRUCTOR ButtonThing (Value AS STRING)
  BASE(Value)
END CONSTRUCTOR

FUNCTION NamedThing.Description$ ()
  Description$ = THIS.Name
END FUNCTION

FUNCTION ButtonThing.Description$ ()
  Description$ = "BUTTON: " + THIS.Name
END FUNCTION

One class can build on another with EXTENDS, and a class has at most one base class. Inside the derived class's constructor, BASE(args) chooses which base constructor to run; leave it out and the base class's zero-argument constructor runs.

Virtual and abstract methods

A VIRTUAL method is one a derived class may replace with its own version. The derived class must mark that version OVERRIDE, so a misspelled name becomes a compile error instead of silently becoming a new, unrelated method.

DIM Item AS NamedThing
Item = NEW ButtonThing("SAVE")
PRINT Item.Description$()   ' dispatches to ButtonThing

ABSTRACT declares a method that every derived class must supply and that the base class does not implement. A class with an abstract method describes what its derived classes promise to do, and you cannot create an object of it directly. Create a derived class that implements every abstract method, and make objects of that.

Properties

A property lets a read or a write look like a plain field while the work happens in explicit getter and setter bodies:

CLASS Meter
  PRIVATE:
    Stored AS INTEGER
  PUBLIC:
    DECLARE PROPERTY Value () AS INTEGER
    DECLARE PROPERTY Value (NewValue AS INTEGER)
END CLASS

PROPERTY Meter.Value () AS INTEGER
  Value = THIS.Stored
END PROPERTY

PROPERTY Meter.Value (NewValue AS INTEGER)
  THIS.Stored = NewValue
END PROPERTY

DIM M AS Meter
M = NEW Meter()
M.Value = 12
PRINT M.Value

An indexed property takes its index parameters first and, for the setter, the new value last:

Box.Cell(3) = 42
PRINT Box.Cell(3)

+= on a property evaluates the object, then the getter, then the right-hand side, then the setter, each exactly once and in that order.

Strong and weak relationships

An ordinary object field holds a strong reference: as long as the field points at an object, that object stays alive. A WEAK field points at an object without keeping it alive:

CLASS DocumentNode
  PUBLIC:
    Child AS DocumentNode
    WEAK Parent AS DocumentNode
END CLASS

This is the classic tree pattern. A parent strongly owns its children; each child weakly points back at its parent. Once a weakly referenced object has been freed, reading the field gives NOTHING, never a reference to memory that is gone.

A class field may hold a number, a variable-length string, an inline TYPE record, or a strong or weak reference to an object. It may not directly hold an array, and it may not hold a handle to a host resource such as an open file or a network connection. Put a collection behind an object with its own methods, and let an object from d/Works, the office suite being written in d/BASIC (d/Write, d/Calc, d/Present), own the lifetime of any host resource on your behalf rather than storing a raw handle in your own object's state.

Static fields

A STATIC field belongs to the class as a whole rather than to any one object:

CLASS Ticket
  PUBLIC:
    STATIC Issued AS LONG
END CLASS

Ticket.Issued = Ticket.Issued + 1

Use static state sparingly. State that lives in objects is easier to test, easier to combine, and easier to run in more than one window at once.

Objects as parameters

When an object is passed BYVAL, the procedure gets its own reference to the same object and cannot change which object the caller's variable names. When it is passed BYREF, the procedure can:

SUB ClearSelection (BYREF Selected AS WorksWidget)
  Selected = NOTHING
END SUB

Either way, changes made to the object itself are visible through every name that refers to it. BYVAL versus BYREF decides whether the procedure may point the caller's variable at a different object; neither one makes a copy of the object.

The rule-of-thumb table

Question Choose TYPE Choose CLASS
Should assignment make an independent copy? ✓
Does it need constructors or methods? ✓
Does identity matter? ✓
Is it a compact shape of data to send or store? ✓
Does it own other objects, or manage the lifetime of a host resource? ✓
Does it need inheritance or virtual methods? ✓

When in doubt, begin with a TYPE. Promote it to a CLASS when identity or behavior becomes part of the problem, not merely because objects sound more modern.


Next: Console I/O & Embedded DATA →