A resource is a value with a responsibility that cannot be duplicated implicitly. An open file is the simplest example: copying its handle into two independent owners could make both close the same operating-system resource. BASIL therefore rejects implicit resource copies and releases each owner once.
Use and release a file
file = OpenFile("/dev/null")
PRINT GetFileInfo(file).SizeOpenFile returns a fresh owner. GetFileInfo borrows it. At scope exit the file
closes automatically; there is no manual free. /dev/null is used to make this
example independent of your personal files. In an application, opening a path
can fail with IoError, which you can catch or declare.
file = OpenFile("/dev/null")
other = fileThis is intentionally rejected. If another function only needs to read or use the file, pass an ordinary borrowed parameter. If it must keep ownership, transfer the owner explicitly.
Transfer to a function
file = OpenFile("/dev/null")
Inspect(TAKE file)
SUB Inspect(TAKE input AS File) FAILS IoError
PRINT GetFileInfo(input).Size
END SUBAfter TAKE, the caller cannot use file until it assigns a new owner. Inspect now
owns input and releases it when finished. A fresh result can be passed directly:
Inspect(OpenFile(path)). A return of a named resource similarly needs TAKE.
Transfer is not rolled back if a later argument fails. Already transferred temporaries are cleaned up; the original binding remains unavailable. Reassignment normally constructs the new value before destroying the old one, but explicitly taking the old owner inside the right-hand side changes that availability.
Compose resources
TYPE Reader
Handle AS File
Name AS STRING
END TYPE
reader = Reader { Handle=OpenFile("/dev/null"), Name="input" }
PRINT reader.Name
PRINT GetFileInfo(reader.Handle).SizeReader automatically becomes noncopyable because it owns a File. Its fields clean up recursively. If construction fails after some fields have completed, those completed owners are released. No incomplete record is exposed to users.
For extra release behavior, a module may define one nongeneric, nonpublic
SUB __Cleanup(BYREF value AS YourType). The compiler invokes it with a complete
record, then releases owned fields. Application code cannot call or take a
function reference to this reserved hook.
TYPE Ticket
Name AS STRING
END TYPE
SUB __Cleanup(BYREF ticket AS Ticket)
PRINT "Released "; ticket.Name
END SUB
SUB Work()
ticket = Ticket { Name="job" }
PRINT "Working"
END SUB
Work()
PRINT "Done"A cleanup hook makes even this otherwise copyable record a resource. Hooks must handle their own operational failures. Explicit checked Finish/Commit/Close operations are still needed when an application must observe successful completion; automatic cleanup is not a promise that a transaction committed. Cleanup may suspend, with cancellation deferred while it completes. Fatal allocation or runtime traps have no normal application-cleanup guarantee.
Optional owners and containers
DIM maybe AS OPTIONAL OF File = SOME(OpenFile("/dev/null"))
IF LET borrowed = maybe THEN
PRINT GetFileInfo(borrowed).Size
END IF
IF LET owned = TAKE maybe THEN
PRINT GetFileInfo(owned).Size
END IFThe first branch loans the payload read-only and protects maybe from conflicting mutation/transfer. The second consumes the optional and owns its present payload. NONE is a valid initialized absence, unlike an unavailable moved binding.
Lists, arrays, optionals and dictionary values can own resources. Those containers become noncopyable; operations requiring copies of resource elements are rejected. Use Pop/ExtractAt to remove list owners and Extract to remove dictionary values. RemoveAt/Remove instead destroy removed owners. Iteration borrows rather than transferring every visited resource.
ready = [OpenFile("/dev/null"), OpenFile("/dev/null")]
files = ToArray(TAKE ready)
PRINT LEN(files)
PRINT GetFileInfo(files(0)).Size
DIM optionalFiles(1) AS OPTIONAL OF File
optionalFiles(0) = SOME(OpenFile("/dev/null"))
REDIM PRESERVE optionalFiles(-1 TO 2)
PRINT LEN(optionalFiles)Optional cells default to NONE. A File itself has no default, so use a list of already initialized owners followed by ToArray when every cell must contain a file. Resource fills cannot duplicate one owner across new cells.
Resource dictionary Get/brackets borrow a view, Find supplies an optional view,
and SetDefault adopts a fresh or explicitly transferred fallback before borrowing
the stored value. Values(BYREF dictionary) permits checked compaction and then a
read-only iteration view. These operations do not manufacture independent resource
copies. Resource keys and resource sets remain unsupported.
Replace or detach fields
TAKE owner.Field REPLACING replacement transfers the previous field and installs
a complete replacement atomically. Failure before commitment preserves the old
field. SWAP exchanges initialized writable same-type bindings, nested fields or indexed cells
without copies or cleanup; swapping an optional field with NONE is useful for
detaching it while keeping its parent complete.
TYPE Pair
Left AS File
Right AS File
Label AS STRING
END TYPE
pair = Pair { Left=OpenFile("/dev/null"), Right=OpenFile("/dev/null"), Label="inputs" }
left = TAKE pair.Left
PRINT pair.Label
pair.Left = OpenFile("/dev/null")
PRINT GetFileInfo(pair.Left).SizeExchange indexed values without copying owners
SWAP now accepts nested fields and list/array elements or existing dictionary
values, including two checked cells in the same container. The equivalent
function-style statement is Swap(BYREF left, BYREF right).
items = ["first", "second"]
snapshot = items
Swap(BYREF items[0], BYREF items[1])
PRINT items[0]
PRINT snapshot[0]
SwapAt(BYREF items, 0, 1)
PRINT items[0]The values exchange without cloning or destroying their payloads. Both selectors evaluate once, left to right. Later selector failure before the exchange preserves the stored values, although selector side effects already performed remain. Exchanging a location with itself is valid. A parent and its own overlapping subobject cannot be exchanged; read-only fields, string characters, slices, set keys and task/channel capabilities are not writable exchange locations.
Direct indexed SWAP retains each container’s normal bounds policy; a missing dictionary key reports MissingKey without insertion. SwapAt is a checked LIST alternative: both indices must be nonnegative and in range, or it raises IndexError before changing storage. It returns the unchanged list length. These dedicated two-location rules do not permit conflicting BYREF arguments to ordinary functions. See storage exchange for exact selector restrictions.
Static field paths in writable local owners can leave checked holes when their containing records have no whole-record cleanup hooks. Other initialized fields remain usable, but whole-record use requires restoration of every hole. A hook could inspect any field, so hooked parents require atomic replacement instead. Indexed element holes and partial holes through borrowed parameters are rejected. Use the field-move reference for detailed flow rules.