Repository navigation
Category Defer
Home › All Categories › Defer
sub Load( byref path as ustring )
dim as HANDLE h = CreateFile( path, ... )
if( h = INVALID_HANDLE_VALUE ) then exit sub
defer CloseHandle( h )
... ' any number of early exits
if( bad ) then exit sub ' CloseHandle runs
if( worse ) then return ' CloseHandle runs
goto finish ' CloseHandle runs
end sub ' CloseHandle runstiko has deterministic destructors, which handle cleanup well -- but only if you declare a type for every cleanup action. A one-off CloseHandle, DeleteObject, ReleaseDC or free in a procedure with four early exits gets written four times, or forgotten once. That is the most common resource-leak shape in Win32 code.
DEFER is for the cleanup that does not justify a type.
defer <statement>- Runs at the exit of the ENCLOSING SCOPE, not of the procedure. - Runs in REVERSE registration order. - Runs on EVERY path out: falling off the end, 'exit sub', 'exit function',
'return', 'exit for/while/do' including 'exit for, for', and 'goto' out of
any number of nested scopes.- In a loop body, runs on EVERY ITERATION -- the loop body is a scope. - Takes ONE statement. There is no block form.
Defers and local destructors are not two passes. They live in one list in declaration order and unwind together:
scope
dim a as Tr : a.nm = "a"
defer print "[1]"
dim b as Tr : b.nm = "b"
defer print "[2]"
end scope
' [2] ~b [1] ~aso a defer can rely on a local declared before it still being alive, and a destructor can rely on a defer registered after it having already run.
Everything the deferred statement reads is read when it RUNS, not when it was written:
dim as int32 v = 7
defer print v
v = 9
' prints 9This is the single most common surprise. It matters most in a loop, where it does not look like a defer question at all:
dim as int32 i = 0
while( i < 3 )
defer print i ' prints 1, then 2 -- not 0, then 1
i += 1
if( i = 2 ) then exit while
wendIf you need the value as it was, copy it into a scope-local first.
This is the one place DEFER here differs from Go's, and it is worth knowing.
The cleanup is derived from the scope's SYMBOL TABLE at compile time. Nothing is recorded at run time, so a 'defer' that was never "reached" still runs:
scope
goto skip
defer print "runs anyway" ' error 349
skip:
end scopeBecause that reads as if the jump skips the defer and it does not, crossing a DEFER with a branch is refused:
error 349: Branch crossing a DEFER statement, to label: SKIPwhich is the same rule tiko applies to jumping past a local with a constructor. What IS honoured is position: a defer placed after an early exit never runs on that exit, because it is outside the exit's statement window.
sub f( )
print "before"
exit sub
defer print "never" ' correctly does not run
end subdefer print "x" ' error 348: DEFER is not allowed at module levelThere is no scope to exit, so nothing would ever run it.
It is NOT a reserved word, so 'defer' remains usable as a name, and a contextual keyword costs nothing to programs that do not use the feature.
It is read as the keyword only when the NEXT token begins a statement. So all of this compiles with its ordinary meaning:
dim defer as int32 = 3
defer = 4
defer += 5 ' note: '+=' lexes as '+' then '='
dim deferArr( 0 to 2 ) as int32
type T : as int32 defer : end type
h.defer = 5
x = defer( 6 ) ' a function named 'defer'THE ONE EXCEPTION: a PAREN-LESS call in statement position to a sub named 'defer', WHEN ITS ARGUMENT BEGINS WITH A NAME:
defer x ' now a DEFER of the statement 'x'
defer 6 ' still a call: a literal begins no statement
defer -1 ' still a call
defer( 6 ) ' still a callEvery call that produces a value is unaffected, because it needs the parentheses anyway. This is the same trade FOR EACH already makes with 'each'.
With
sub defer( byval n as int32 )declared: tiko calls the sub fordefer 6,defer -1anddefer( 6 ), and readsdefer xas a DEFER. It compiles the statementxand drops it. The variable, array and member forms above work.
tiko reports TKO0814 for a defer directly at module level, or directly in a namespace. One inside a block at module level (scope, if, for, do, while, select) is accepted, as one in a procedure is. tests/diagnostics/defer-module-level.test holds that.
tiko also reports:
-
TKO0828: adeferwith nothing after it on the line.defer :first on a line is the LABELdefer:, unless a procedure is nameddefer; then, and anywhere else on a line, it isTKO0828. -
TKO0827: a FORWARDgotopast adeferwhose scope holds the label, reported on thegoto's line once per defer crossed. A backward jump, a jump out of the defer's scope, a defer whose scope has closed before the label (a nestedscope, the other branch of anif, a single-lineif's own scope) andon ... gotoare accepted.
tests/diagnostics/defer-goto-errors.test, defer-bare.test and defer-label.test hold these.
tiko's parser recognises the contextual keyword (src/parser/parser.tiko), and the MIR lowering (src/mir/lower.tiko) registers the deferred statement in the same per-scope cleanup list that local destructors are registered in, so reverse order and the interleaving with destructors come from that one list.
The DEFER tests are vendored in tiko-lang under tests/vendor/fbc-tests/.
generics/defer.bas is exhaustive: reverse order, fallthrough off 'end scope' / 'end sub' / 'end function', every break path including 'exit for, for' and 'goto' out of three nested scopes, a loop body, defers in IF / SELECT / WITH arms, interleaving with destructors asserted in BOTH orders and on BOTH the fallthrough and break paths, a defer that is never reached, and 'defer' as a variable, array, field and shared.
Order is asserted by accumulating into a string and comparing the exact sequence. A defer that runs at the wrong time still runs, so only the sequence distinguishes right from wrong.
generics/defer-identifier.bas covers 'defer' as a PROCEDURE name, in a file of its own.
errors/defer-errors.bas holds the golden output for all three diagnostics. tiko reports all three -- see Diagnostics.
Under tiko: defer.bas and defer-identifier.bas build and run with exit 0 and no output, because every assertion passes. tiko's own suite adds tests/mir/defer-contextual.test (the contextual keyword) and tests/mir/cleanup.test (defer order against destructors, checked against the traces in tests/semantic/cleanup/).
tiko-lang · Home · Feature Index · Keyword Index · License
FreeBASIC manual pages reproduced under the GNU FDL 1.3.
- Generics
- Interfaces
- For Each
- Iterators
- Tuples
- Array Results
- Defer
- Lambdas
- Block Strings
- String Slices
- Embed
- Typed Variadics
- C Header Import
- Foreign Library Linking
- UString
- Fixed-Width Types
- Nullptr
- String Library
- Format
- Array
- Map
- Set
- LinkedList
- OrderedMap
- OrderedSet
- Sorting
- Optional
- Result
- Smart Pointers
- Date and Time (Chrono)
- Filesystem
- File Streams
- Config (INI and TOML)
- Codec
- Hash
- UUID
- OS
- Process
- Net
- HTTP and TLS
- Math Library
- Finance
- Logging
- Terminal TUI Library
- PsControls
- tkControls
- sys/win
- Goroutines and Channels
- LLVM Backend
- tiko build
- TIKO_COMPILER
- Variable Declarations
- Standard Data Types
- User Defined Types
- Procedures
- Operators
- Control Flow Statements
- Converting Data Types
- Array Functions
- String Functions
- Mathematical Functions
- Bit Manipulation
- Date and Time Functions
- File I/O Functions
- Console Functions
- User Input (Runtime Library)
- User Input Functions (Graphics Library)
- Screen Functions
- 2D Drawing Functions
- Graphics Library Reference
- Graphics Keyword List
- Memory Functions
- Threading Support Functions
- Error Handling Functions
- Operating System Functions
- Preprocessor
- Intrinsic Defines
- Miscellaneous Keywords
- Modularizing