Skip to content

Errors, refusals & diagnostics

Programs fail in different ways. A misspelled name is not the same event as a full disk, and “record not found” is not the same event as malformed bytecode. D/BASIC keeps those distinctions visible so recovery code does not have to guess what happened.

The three moments of failure

Moment Example What happens
compile unknown name, wrong type, unmatched block no DBC image is produced; fix the source
load/admission missing capability, incompatible DBC version, unresolved library program code does not begin
runtime bounds, capacity, stale handle, refused Intent, explicit ERROR active ON REFUSAL handler runs, or the program stops with the refusal

Compile and admission failures cannot be caught by the program that never started. Runtime refusals can be handled only when the program has a truthful alternative.

Catch a runtime refusal

ON REFUSAL GOTO CouldNotLoad

CALL LoadOptionalReport
PRINT "REPORT READY"
GOTO Done

CouldNotLoad:
  PRINT "REPORT UNAVAILABLE. RULE "; ERR; " LINE "; ERL
  RESUME Done

Done:
END

ON REFUSAL GOTO target establishes the handler. The target may be a named label or numbered line. A handler should either finish the current job or use a RESUME form; falling through and pretending the failed operation succeeded is almost always a bug.

The handler is program-wide, not a lexical try block. Refusal handling is also deliberately non-reentrant: if an operation inside the active handler refuses while the first refusal is still in flight, execution terminates rather than recursively entering the same handler. Keep handler work small and make its cleanup operations safe to repeat.

Inspect ERR and ERL

Value Meaning
ERR stable nonzero rule identifier for the refusal in flight
ERL associated numbered source line, or 0 when no numbered line is available
ProblemRule% = ERR
ProblemLine% = ERL
PRINT "RULE "; ProblemRule%; " AT "; ProblemLine%

Outside a refusal handler, both values are empty observations (0). Treat the rule identifier as data: log it, compare it when a particular recovery really depends on it, and include it in a useful bug report. Do not replace it with a fragile comparison against prose produced by one host.

Resume deliberately

Form Meaning
RESUME NEXT continue after the refused operation
RESUME target leave the handler at a named label or numbered line

Prefer an explicit target when the failed operation would have established state needed by the next statement:

ON REFUSAL GOTO NoOptionalSound
CALL OpenOptionalSound
SoundReady% = -1
GOTO ContinueSetup

NoOptionalSound:
  SoundReady% = 0
  RESUME ContinueSetup

ContinueSetup:
PRINT "SETUP COMPLETE"

RESUME NEXT is best for a self-contained optional action whose following code does not assume a result. It is not a general “ignore errors” switch.

Raise a governed rule intentionally

ERROR raises one literal nonzero rule ID:

' Local experiment only: private rule &HF001.
IF Width% < 0 THEN ERROR &HF001

The operand is a numeric literal in 1..65535; a variable or CONST name is not accepted there. ERR is an INTEGER, so it exposes the same 16 rule bits as a signed value: &HF001 displays as -4095. Compare a private rule with the same hexadecimal literal—IF ERR = &HF001 THEN—rather than decimal 61441. Syntax acceptance does not allocate ownership of that number. A rule that can reach a shipping image must be registered in D/BASIC's append-only rule register and the literal must name exactly that governed condition.

The private experimental band is 61440..65534 (&HF000..&HFFFE). It is useful while designing a local invariant, but it must not appear in a shipped image or published conformance trace. Move the condition to its allocated registered ID before release. 0 means no refusal and 65535 is never allocated.

Use ERROR for an impossible or explicitly refused state, not for ordinary branching and not as an ad-hoc substitute for a result value.

Expected outcomes are often values, not refusals

A well-designed external operation distinguishes an expected result from a broken contract:

  • FILE STAT may report that a name is absent as a normal status;
  • a DATA query may return zero rows;
  • a network response may carry an HTTP status;
  • a Save transaction may still be pending; and
  • cancel may be an ordinary terminal outcome.

Those belong in result records and application logic. Malformed carriers, stale bindings, unavailable required capabilities, invalid bounds, and violated integrity rules are refusals. Catching every non-success as one undifferentiated error would throw away information the user needs.

Cleanup is part of the control flow

Anything opened or retained should have one visible cleanup path:

DIM SessionOpen AS INTEGER

ON REFUSAL GOTO Failed
CALL OpenSession
SessionOpen = -1
CALL DoWork
GOTO Cleanup

Failed:
  PRINT "WORK REFUSED. RULE "; ERR
  RESUME Cleanup

Cleanup:
  IF SessionOpen THEN CALL CloseSession
END

For owner objects, put idempotent close/cancel behavior in a method and call it from both the normal path and the window's CLOSE face. Destructors remain a last deterministic release point, not a substitute for reporting whether an external commit succeeded.

Do not catch what you cannot repair

Good reasons to install a handler:

  • an optional capability has a real fallback;
  • a transaction can be cancelled and the prior document retained;
  • a user can choose a different file or retry after freeing space;
  • a GUI can keep its last verified model visible; or
  • a console tool can add context before finishing.

Bad reasons:

  • hiding a type or bounds bug;
  • continuing with output from an operation that never completed;
  • converting every refusal to “not found”; or
  • looping forever on a capability the host has explicitly refused.

Reading compiler diagnostics

A useful diagnostic answers four questions:

  1. Where? File, line, and column or source span.
  2. What? The name, type, shape, or construct that could not be accepted.
  3. Why? A stable code or governing rule.
  4. What next? The nearest valid spelling or architectural boundary.

Fix the first diagnostic first; later errors may be consequences of the same missing token. In the playground, selecting a diagnostic moves the source caret to its position. In command-line work, preserve the stable diagnostic code in automation and show the human explanation to the person running the build.

Errors in GUI applications

Never spin, retry, or open a modal alert from DRAW. Store the terminal outcome in the model, request invalidation, and let the next frame present it. Keep Cancel and Close reachable while an external operation is pending. A useful status surface says what failed and what the person can do—not merely “Error.”

SUB ReportFailure (BYVAL Rule AS INTEGER)
  StatusText$ = "COULD NOT SAVE. RULE " + LTRIM$(STR$(Rule))
  SERVICE INVALIDATE()
END SUB

A refusal checklist

  1. Decide whether the condition is an expected result or a refusal.
  2. Catch only when a real recovery or cleanup path exists.
  3. Read ERR and ERL before leaving the handler.
  4. Never execute success-only code after a refused operation.
  5. Resume to an explicit safe point when state may be incomplete.
  6. Keep the last verified user-visible state authoritative.
  7. Close or release every owned session on success, cancel, and refusal.
  8. Preserve stable rule identifiers in tests and reports.

Next: Files & File Manipulation →