Skip to content

Arrays

An array is one name for an ordered field of values. D/BASIC preserves BASIC's wonderfully direct subscripting while making whole-array work visible enough for a compiler and a tiny runtime to do it well.

Declare an array

DIM Scores%(10)
DIM Temperatures(1 TO 12) AS DOUBLE
DIM Board(0 TO 7, 0 TO 7) AS INTEGER

Each dimension can name only an upper bound or an explicit lower TO upper pair. OPTION BASE supplies an omitted lower bound:

OPTION BASE 1
DIM Months$(12)          ' 1 TO 12
DIM Samples%(0 TO 255)  ' Explicitly zero-based

D/BASIC supports up to eight dimensions. Bounds and subscripts are converted to INTEGER.

Read and write elements

Board%(3, 4) = 9
PRINT Board%(3, 4)

An element acts like a variable of the array's element type. Assignment conversion happens before storage.

Stay inside the declared bounds. D/BASIC checks rather than allowing an out-of-range index to wander into unrelated memory.

Ask for the bounds

LBOUND and UBOUND report the live bounds. Pass a second argument for a particular dimension:

FOR Row% = LBOUND(Board%, 1) TO UBOUND(Board%, 1)
  FOR Column% = LBOUND(Board%, 2) TO UBOUND(Board%, 2)
    PRINT Board%(Row%, Column%);
  NEXT Column%
  PRINT
NEXT Row%

Without the dimension argument, dimension 1 is used.

Resize and erase

REDIM gives a dynamic array a new shape:

REDIM Samples%(0 TO NewLast%)

REDIM PRESERVE keeps the overlapping values while changing the allowed bounds:

REDIM PRESERVE Samples%(0 TO NewLast%)

REDIM PRESERVE does not apply to arrays of TYPE records. Use ordinary REDIM for an atomic replacement, or copy record elements explicitly when preserving them is part of your algorithm.

ERASE releases an array binding and leaves it null:

ERASE Samples%

That null state matters to the whole-array verbs below. It is a real state, not an empty rank-one array whose upper bound happens to be -1.

Whole-array parameters

Empty parentheses identify a whole-array carrier:

DECLARE FUNCTION Sum& (Values() AS INTEGER)

FUNCTION Sum& (Values() AS INTEGER)
  DIM Total AS LONG
  FOR I% = LBOUND(Values) TO UBOUND(Values)
    Total = Total + Values(I%)
  NEXT I%
  Sum& = Total
END FUNCTION

The callee sees the live bounds. A whole-array parameter is not an element, a slice, or an untyped bag.

The three whole-array verbs

Intent Syntax What moves Allocation shape
copy ARRAYCOPY Source() TO Destination() every element into an independent binding destination becomes a fresh copy
fill ARRAYFILL Destination(), Value one converted value to every element same element type, rank, and bounds; atomic replacement
swap ARRAYSWAP A(), B() only the two bindings no element copy and no new array allocation

All operands shown with () must be bare whole arrays. A(), not A, A(I), Object.A(), or a subrange.

The whole-array verbs accept arrays whose elements are numeric scalars, variable STRINGs, or CLASS references. They do not accept arrays of TYPE records. Copy, initialize, or exchange record elements explicitly so the record-value boundary remains visible.

ARRAYCOPY: make an independent array

DIM Draft%(1 TO 4)
DIM Published%()

Draft%(1) = 10
Draft%(2) = 20
ARRAYCOPY Draft%() TO Published%()

Draft%(1) = 99
PRINT Published%(1)   ' 10: this is a separate binding

Use a copy when both versions must remain independently mutable.

ARRAYFILL: one meaning, every element NEW

DIM Plane%(1 TO 4)
ARRAYFILL Plane%(), -1

DIM Names$(0 TO 2)
ARRAYFILL Names$(), "UNCLAIMED"

The value is converted exactly as one element assignment would be:

ARRAYFILL Plane%(), 2.6   ' each INTEGER receives 3

Fill keeps the destination's element type, rank, and bounds. It is transactional: if the operation refuses partway through preparation, the old binding remains authoritative rather than exposing a half-filled array. This can require one temporary array-sized allocation.

Filling an ERASEd/null destination refuses because there is no shape to fill.

Why it is a language verb

This loop is readable:

FOR I% = LBOUND(Plane%) TO UBOUND(Plane%)
  Plane%(I%) = -1
NEXT I%

But ARRAYFILL Plane%(), -1 says more. It promises that element order is irrelevant, exposes the operation to the optimizer, lowers to one DBC instruction, and lets a runtime use the most appropriate memory route while retaining atomic semantics.

ARRAYSWAP: change ownership, not elements NEW

DIM Front%(1 TO 1000)
DIM Back%(1 TO 1000)

' Draw from Front while preparing the next state in Back.
ARRAYFILL Back%(), 0
' ...modify Back()...
ARRAYSWAP Front%(), Back%()

ARRAYSWAP exchanges the bindings. Contents, lower and upper bounds, and rank travel with each binding. No elements are copied, allocated, retained, or released.

The arrays must have the same element type. When both are live, they must have equal rank; their individual bounds may differ:

DIM Live%(1 TO 3)
DIM Held%(0 TO 1)

ARRAYSWAP Live%(), Held%()
PRINT LBOUND(Live%); UBOUND(Live%)  ' 0  1

A null binding is legal and swaps as null. That makes a fast “take this array and leave nothing” handoff possible:

DIM Ready%(1 TO 3)
DIM Taken%(1 TO 3)
ERASE Taken%

ARRAYSWAP Ready%(), Taken%()
' Taken now owns the former Ready array; Ready is null.

The memory trick to remember

Fill changes the values. Swap changes the name that owns them. If you find yourself copying a large back buffer merely to make it current, you probably meant ARRAYSWAP.

Classic scalar SWAP

SWAP exchanges two scalar or compatible record locations:

SWAP Left%, Right%

It is intentionally different from ARRAYSWAP, whose operands are whole array bindings.

Arrays of records and objects

TYPE Point
  X AS INTEGER
  Y AS INTEGER
END TYPE

DIM Path(0 TO 31) AS Point
DIM Buttons(0 TO 7) AS WorksButton

Record elements are inline values. Class elements are object references initialized to NOTHING; assigning an object stores a counted reference. The difference is explored in Records & Objects.

These arrays are ordinary containers, but the three whole-array verbs above apply only to numeric, variable-string, and class-reference arrays—not to TYPE record arrays.

Patterns worth teaching

A ring without mysterious memory

NextSlot% = (NextSlot% + 1) MOD Capacity%
Samples%(NextSlot%) = Reading%

A table driven by bounds

FOR I% = LBOUND(Names$) TO UBOUND(Names$)
  PRINT Names$(I%)
NEXT I%

Do not repeat 1 TO 100 in every loop. Let the array report its own truth.

Front/back state

Prepare in Back(), then ARRAYSWAP Front(), Back(). Readers either see the old complete state or the new complete state, never your construction site.

Quick rules

  • Bounds are inclusive.
  • The default lower bound is 0 unless one module-level OPTION BASE 1 says otherwise.
  • Use LBOUND and UBOUND in reusable procedures.
  • Use ERASE to release the binding.
  • Use ARRAYCOPY for independence, ARRAYFILL for a uniform state, and ARRAYSWAP for an ownership exchange.
  • Use those three verbs only with numeric, variable-string, or class-reference element types.
  • Whole-array parentheses are not decoration; they distinguish the binding from an element.

Next: Records & Objects →