Documentation/Chapters

Checked errors and recovery

On this page

An operation can fail for an ordinary reason: a missing file, malformed JSON or a request rejected by an application rule. Checked errors make these outcomes part of a function’s contract. They are separate from successful results, optional absence, cancellation and fatal runtime traps.

Declare and raise an error

ERROR InvalidAge
    Value AS INTEGER
END ERROR
FUNCTION ValidateAge(value AS INTEGER) AS INTEGER FAILS InvalidAge
    IF value < 0 THEN
        RETURN ERROR InvalidAge { Value=value }
    END IF
    RETURN value
END FUNCTION
TRY
    PRINT ValidateAge(-3)
CATCH problem AS InvalidAge
    PRINT "Invalid age: "; problem.Value
END TRY
PRINT "Continuing"

FAILS lists errors that can leave the function. RETURN ERROR selects failure; ordinary RETURN selects success. Plain calls automatically check failure, even when you discard the successful result. Inline TRY on a call is optional legacy syntax, not a requirement to activate checking.

Catch here or propagate

Use TRY/CATCH when this part of the program can recover, add context or report a useful result. Otherwise declare the escaping error in the enclosing function’s FAILS clause. Merely ignoring a return value cannot hide a checked error.

ERROR MissingItem
    Name AS STRING
END ERROR
FUNCTION Lookup(name AS STRING) AS STRING FAILS MissingItem
    RETURN ERROR MissingItem { Name=name }
END FUNCTION
FUNCTION Title(name AS STRING) AS STRING FAILS MissingItem
    RETURN UPPER(Lookup(name))
END FUNCTION
TRY
    PRINT Title("report")
CATCH problem AS MissingItem
    PRINT "Missing: "; problem.Name
END TRY

Failure skips the remaining successful path to the matching handler or caller. Owned locals and partial temporaries clean up. Errors thrown inside a catch go to outer handlers or propagate, rather than re-entering a sibling catch of that same TRY. RETURN ERROR problem forwards a caught error. Catch bindings are read-only owners; independently copy a copyable error to edit it before forwarding.

The implicit top-level boundary reports an unhandled error after cleanup and exits

  1. An explicit Main still needs to declare its escaping errors. Keep lower-level functions precise; let the application boundary decide reporting policy.

Error families

ERROR OperationFailure
    Message AS STRING
END ERROR
ERROR MissingDocument EXTENDS OperationFailure
    Path AS STRING
END ERROR
TRY
    Load()
CATCH problem AS OperationFailure
    PRINT problem.Message
END TRY
SUB Load() FAILS MissingDocument
    RETURN ERROR MissingDocument { Message="Cannot load", Path="notes.txt" }
END SUB

Catching a parent handles descendants and exposes shared fields. Copying or forwarding a parent view preserves the concrete error and its complete payload. The common root Error has no universal Message or Code field. Prefer specific types when they let you handle the cause accurately. Broad-catch warnings show declared error sets and call/callback origins; they do not require UNSAFE.

Callback-dependent failures

A wrapper should preserve a callback’s actual checked failures, rather than silently replace them with a generic InvocationError or widen every call to Error.

ERROR SigningFailure
    Message AS STRING
END ERROR
SUB Invoke(action AS CALLABLE() FAILS (Error)) FAILS LIKE action
    action()
END SUB
SUB Sign() FAILS SigningFailure
    RETURN ERROR SigningFailure { Message="Rejected" }
END SUB
TRY
    Invoke(Sign)
CATCH problem AS SigningFailure
    PRINT problem.Message
END TRY

FAILS LIKE action admits the supplied callback’s checked set. The same mechanism can follow callbacks stored in configuration records and containers. AS LIKE argument returns an owned value retaining its contained contracts; it is not a borrowed lifetime. AS Wrapper LIKE argument carries admitted errors into a wrapper shape. Signature references such as FAILS LIKE Decode(AS STRING) compose contracts without executing application code. The reference part explains these advanced forms and their limits.

What a catch cannot recover

Cancellation is a control path for stopping a task; ordinary typed CATCH does not intercept it. Fatal allocation failure, stack exhaustion, checked integer traps and direct bounds traps have no normal recoverable-cleanup promise. A missing optional value is NONE, not automatically an error. Model these distinctions in your application instead of catching Error and assuming every possible failure has become recoverable.

From the BASIL documentation. View source ↗

Find your way around BASIL

Type to search the manual.