4.3. Very safe context

Daslang prioritizes runtime performance and development speed over absolute safety. Outside of unsafe blocks the language enforces safety checks that prevent common programming errors, but certain fundamentally unsafe patterns cannot be eliminated without sacrificing performance.

options very_safe_context mitigates the most dangerous of these patterns.

4.3.1. The problem: aliased references

Some expressions produce multiple references into the same container, where an earlier reference can be invalidated by a later operation. Consider the following example:

options gen2
options very_safe_context

struct Foo {
    a : int
}

def move_data(var a, b : Foo) {
    b <- a
}

def good_index(var a : array<Foo>; index : int) : int {
    if (index >= length(a)) {
        resize(a, index + 1)
    }
    return index
}

[export]
def main() {
    var data : array<Foo>
    data[good_index(data, 5)].a = 42
    move_data(data[good_index(data, 5)], data[good_index(data, 100)])
    print("data = {data}\n")
}

Both data[5] and data[100] must share the same lifetime, but 5 is evaluated before the resize and 100 after it. No order of operations can make this code correct with unboxed containers and pass-by-reference semantics. Equivalent C++ code exhibits the same behavior.

The issue is even more apparent with tables:

tab[key1] <- tab[key2]  // may rehash the table, invalidating the key1 reference

4.3.2. What very_safe_context does

When options very_safe_context is enabled, array and table memory is not freed on resize. Instead, the old buffer is left for the garbage collector to reclaim later. This prevents most crashes and often produces correct results because existing references continue to point at valid — though possibly stale — copies of the data.

Completely eliminating this class of bugs would require either boxing all containers or introducing a borrow checker, neither of which is practical for the language’s performance goals.

Some of these issues can also be caught by linting or optional runtime checks.

4.3.3. Iteration and container moves

A related hazard occurs when a container is moved while it is being iterated:

var a <- [1, 2, 3, 4]
var b : array<int>
for (i, x in count(), a) {
    if (i == 0) {
        b <- a
    }
    x++
}

This example throws a runtime error, but only at the exit of the loop. Detecting it earlier would require expensive per-iteration or per-move checks. In the meantime, the loop body operates on a shadow copy of the data.