When a program creates a list, some memory must hold its elements. When it opens a file, the operating system supplies a handle that eventually needs closing. Ownership answers who is responsible for keeping that storage or resource alive and releasing it. Borrowing lets other code use it temporarily while the owner remains responsible.
BASIL performs this bookkeeping automatically and checks access before running your program. There is no general garbage collector to wait for. Ordinary owned values are released deterministically as their scopes end, including on checked failure and cancellation. You mainly express intent through assignment and parameter modes, rather than through memory addresses.
Ordinary assignment gives independent values
original = [10, 20]
copy = original
copy[0] = 99
PRINT original[0]
PRINT copy[0]Think of copy = original as giving copy its own value. Nested strings, records
and collections follow the same rule: mutating the destination cannot silently
change the source. The compiler may eliminate work when behavior stays identical,
but you never need to guess whether two ordinary assignments share mutable data.
This differs from languages where assigning a list merely copies a reference to one shared object. It also differs from assuming every assignment behaves like a Rust move. Copyable BASIL values keep independent assignment semantics.
Reading does not require a copy
values = [10, 20, 30]
PRINT Total(values)
PRINT values[0]
FUNCTION Total(items AS LIST OF INTEGER) AS INTEGER
result = 0
FOR EACH item IN items
result += item
NEXT
RETURN result
END FUNCTIONAn ordinary parameter gives temporary read-only access to the caller’s value. The caller remains the owner; Total does not need an independent list just to read three integers. That access is a borrow. Returning an ordinary owned list from a borrowed input would create an independent result unless the function explicitly declares a borrowed return.
Read-only does not mean globally frozen forever. It means that while this access is active, another access must not mutate, relocate, transfer or destroy the storage in a conflicting way. The rule applies through helpers and suspension. Otherwise an apparently innocent read could use an address invalidated by another operation.
BYREF grants exclusive writable access
values = [10, 20]
IncreaseFirst(BYREF values)
PRINT values[0]
SUB IncreaseFirst(BYREF items AS LIST OF INTEGER)
items[0] += 1
END SUBThe caller explicitly grants writable access. For the duration of the call, the function must be able to rely on the absence of conflicting aliases. An alias is another way to reach the same storage, such as a list and one of its elements.
number = 1
Update(BYREF number, BYREF number)
SUB Update(BYREF first AS INTEGER, BYREF second AS INTEGER)
first += 1
second += 1
END SUBThis is deliberately rejected. Do the two operations sequentially, or redesign the function to accept one writable value. The same principle rejects passing an array element while another argument resizes its parent, or mutating a collection during a protected iteration.
BYREF can address writable list/array elements, fields and existing dictionary values. The helper updates actual stored data; it does not edit a detached copy. Missing dictionary locations fail without insertion. The containing root remains protected through later argument evaluation, the call and suspension.
BYVAL gives the callee an owner
values = [3, 1, 2]
PRINT FirstSorted(values)
PRINT values[0]
FUNCTION FirstSorted(BYVAL items AS LIST OF INTEGER) AS INTEGER
Sort(BYREF items)
RETURN items[0]
END FUNCTIONBYVAL creates a writable local owner. A borrowed copyable argument is snapshotted at evaluation, before later argument effects. Fresh owned results can be adopted directly. Use BYVAL when a function genuinely needs its own mutable copy, and an ordinary parameter when it only reads. Choosing the mode deliberately avoids unnecessary copies without introducing shared mutation.
| Parameter mode | Caller intent | Callee access |
|---|---|---|
| ordinary | Let this function read my value | Borrowed, read-only |
| BYREF | Let this function update this storage | Exclusive writable borrow |
| BYVAL | Give this function an independent local value | Writable owner |
| TAKE formal | Give this function ownership of a resource | Writable resource owner |
Moving with TAKE
Some things cannot sensibly be copied: a file, socket, database connection or
user-defined resource. TAKE value transfers its ownership. The old binding
becomes unavailable until reinitialized. BASIL never infers a resource transfer
because a variable appears unused afterward.
Anonymous functions can own independent snapshots of copyable outer values. That is a copy into a closure, rather than a stored borrow or implicit resource transfer; chapter 13 demonstrates when the snapshot is taken. Resources and task/channel capabilities cannot be captured. APIs needing retained resource state, such as logging sinks, take an explicit owned context instead.
TAKE also supports whole writable copyable locals, when explicit transfer is useful. For ordinary data it is usually simpler to let independent-value assignment and compiler copy elimination do their jobs. A TAKE formal itself requires a resource-owning type. Chapter 10 develops resource examples.
Borrowed results and lifetimes
A lifetime is the interval during which storage is valid for an access. BASIL normally infers it. A function returning a view declares the owners from which that view can come:
TYPE Label
Text AS STRING
END TYPE
FUNCTION First(items AS LIST OF Label) AS BORROW Label FROM items
RETURN items[0]
END FUNCTION
labels = [Label { Text="Ready" }]
PRINT First(labels).TextFirst borrows a record from labels instead of manufacturing another owner. The result cannot outlive its origin, become writable access, or smuggle out a reference to a local that has already been destroyed. Copyable results can be independently copied. Stable named owners are required for borrowed origins; retain a fresh collection in a local before asking it for a returned view.
FROM a OR b declares two possible origins and protects both, even when a
particular run returns only one. General stored references, mutable view objects
and arbitrary reference graphs remain unsupported. BORROW is a precise advanced
contract, not a way to bypass normal ownership checks.