Documentation/Reference

Optional fallback and parameter defaults

On this page

The maintained compiler implements lazy optional fallback with ??:

TYPE Config
    Limit AS INTEGER=20
END TYPE
FUNCTION Run(config AS OPTIONAL OF Config=NONE) AS INTEGER
    DIM config=config ?? Config {Limit=20}
    RETURN config.Limit
END FUNCTION
PRINT Run()
PRINT Run(SOME(Config {Limit=42}))

OPTIONAL OF T ?? T produces T. A present value wins, including zero, FALSE, an empty STRING or an empty collection. Only absence evaluates the fallback. Nested optionals unwrap one layer. The operator associates to the right and binds below OR, AND, comparisons and arithmetic. Use parentheses when unwrapping multiple optional layers: (outer ?? SOME(7)) ?? 9.

Failures from a fallback follow normal checked contracts and cleanup. Its static contract remains checked even when a particular runtime value skips it. A fresh owned optional can donate its payload; a named resource optional needs explicit TAKE. A borrowed copyable value produces an independent value when ownership is needed. No implicit truthiness, ownership transfer or resource copying is added.

Reusing a parameter name

An explicit DIM may shadow an ordinary or BYVAL parameter. Its initializer reads the original parameter; subsequent statements see the new local. The local may have a different type. This works in functions, SUBs and block lambdas. BYREF and TAKE parameters cannot be shadowed. Duplicate locals in one scope remain errors; leaving a nested scope restores the original parameter’s visibility.

The original owner and any older borrows keep their identity and lifetime. A new local does not acquire the original parameter’s escaping BORROW provenance. Without DIM, an attempted parameter reassignment/type change suggests the explicit DIM declaration instead of silently changing its meaning.

Aggregate defaults

When omission simply means using a default, an optional is unnecessary:

TYPE Config
    Limit AS INTEGER=20
    Names AS LIST OF STRING=[]
END TYPE
FUNCTION Run(config AS Config=Config {Limit=20,Names=["default"]}) AS INTEGER
    RETURN config.Limit
END FUNCTION

Defaults admit correctly typed scalar constant expressions, records, choices, typed error values, nested lists, empty LIST()/SET()/DICT(), SOME/NONE and scalar static-table constants. Required parameters precede defaults. Arbitrary function calls and mutable bindings are not constant defaults. Generic default recipes are checked when their concrete omitted value is needed; generic bodies retain their ordinary instantiation checks.

Supplied arguments execute once in written order. Only omitted defaults are constructed afterward, in parameter order. Constants and constructor names resolve in the declaration’s module, including moved source-free .blibs and opaque records. Caller locals cannot capture them. Use braces when an ordinary function shares a constructor name; a default cannot silently substitute constructor behavior for that function call. Ordinary supplied arguments retain readonly borrowing. BYVAL defaults are independent owned arguments, just as supplied BYVAL arguments are independent snapshots. BYREF/TAKE defaults remain rejected.

Each required mutable owner has independent storage on every call. Fresh resource literal defaults are constructed and cleaned normally; they do not copy an existing resource. Task defaults use the same typed recipes and owned startup/cleanup rules. NONE can initialize an optional resource without creating a live handle.

From the BASIL documentation. View source ↗

Find your way around BASIL

Type to search the manual.