Memory Management
How Strada handles memory allocation, references, and cleanup.
Automatic Reference Counting
Every value in Strada has a reference count. When a value is assigned to a variable or passed to a function, the count increases. When a variable goes out of scope, the count decreases. When the count reaches zero, the memory is freed.
func example() void {
my str $s = "hello"; # refcount = 1
my str $t = $s; # refcount = 2 (shared)
# When function returns:
# $t goes out of scope: refcount = 1
# $s goes out of scope: refcount = 0 -> freed
}
Inspecting Reference Counts
Use refcount() to see how many references exist to a value:
my array @arr = [1, 2, 3];
say(refcount(\@arr)); # 1
my scalar $ref = \@arr;
say(refcount(\@arr)); # 2
Scope and Lifetime
Variables are scoped to the block where they're declared:
func main() int {
my str $outer = "outer";
if (1) {
my str $inner = "inner";
say($outer); # OK: outer is visible
say($inner); # OK: inner is visible
}
# $inner is freed here
say($outer); # OK
# say($inner); # Error: $inner not in scope
return 0;
}
Loop Variable Scope
for (my int $i = 0; $i < 10; $i++) {
# $i exists here
}
# $i is freed here
foreach my str $item (@items) {
# $item exists here
}
# $item is freed here
References
References allow multiple variables to point to the same data. Creating a reference increases the refcount:
my array @data = [1, 2, 3];
my scalar $ref = \@data; # Reference to @data
# Both point to the same array
push(@data, 4);
say($ref->[3]); # 4
# Array freed only when BOTH go out of scope
Anonymous References
Anonymous arrays and hashes are created with refcount 1:
my scalar $arr = [1, 2, 3]; # Anonymous array ref
my scalar $hash = { "a" => 1 }; # Anonymous hash ref
# Freed when $arr and $hash go out of scope
Object Destruction (DESTROY)
When an object's refcount reaches zero, Strada automatically calls its DESTROY method if one exists:
package Connection;
func new(str $host) scalar {
my hash %self = {
"host" => $host,
"socket" => sys::socket_client($host, 80)
};
return bless(\%self, "Connection");
}
func DESTROY(scalar $self) void {
# Called automatically when object is freed
say("Closing connection to " . $self->{"host"});
sys::close_fd($self->{"socket"});
}
func main() int {
{
my scalar $conn = Connection::new("example.com");
# Use connection...
}
# DESTROY called here automatically
return 0;
}
$self->SUPER::DESTROY() to call parent destructors.
Manual Memory Control
Sometimes you need explicit control over when memory is freed:
undef() - Clear a Variable
my str $big_string = slurp("huge_file.txt");
# Process the string...
undef($big_string); # Free memory now, don't wait for scope end
release() - Free via Reference
my scalar $ref = [1, 2, 3];
release($ref); # Decrements refcount, $ref becomes undef
Performance Optimization
Pre-allocating Arrays
If you know an array's size in advance, pre-allocate to avoid reallocations:
# Method 1: Declaration with capacity
my array @data[10000]; # Pre-allocate for 10,000 elements
# Method 2: Reserve after creation
my array @items = [];
sys::array_reserve(@items, 5000);
# Now push() won't need to reallocate
for (my int $i = 0; $i < 5000; $i++) {
push(@items, $i);
}
Pre-allocating Hashes
# Declaration with capacity
my hash %cache[1000]; # Pre-allocate buckets for ~1000 keys
# Or set default for all new hashes
sys::hash_default_capacity(500);
StringBuilder for String Building
String concatenation creates new strings each time. For building large strings, use StringBuilder:
# Inefficient: O(n²) - creates many intermediate strings
my str $result = "";
for (my int $i = 0; $i < 10000; $i++) {
$result = $result . "line " . $i . "\n";
}
# Efficient: O(n) - appends to buffer
my scalar $sb = sb::new();
for (my int $i = 0; $i < 10000; $i++) {
sb::append($sb, "line " . $i . "\n");
}
my str $result = sb::to_string($sb);
Array Capacity Functions
| Function | Description |
|---|---|
sys::array_capacity(@arr) | Get current allocated capacity |
sys::array_reserve(@arr, $n) | Ensure capacity for at least $n elements |
sys::array_shrink(@arr) | Shrink capacity to match current length |
Memory and Closures
Closures capture variables by reference. The captured variables stay alive as long as the closure exists:
func make_counter() scalar {
my int $count = 0;
return func () {
$count++; # Captures $count by reference
return $count;
};
}
my scalar $counter = make_counter();
# $count from make_counter() is kept alive by the closure
say($counter->()); # 1
say($counter->()); # 2
undef($counter);
# Now $count is finally freed
undef().
Circular References
Reference counting cannot automatically free circular references:
# This creates a cycle that won't be freed!
my hash %a = ();
my hash %b = ();
$a{"other"} = \%b;
$b{"other"} = \%a;
# Both have refcount 2, won't reach 0
# Fix: Break the cycle before scope exit
delete(%a, "other");
# Now %b refcount = 1, %a refcount = 1
# Both freed when scope exits
Weak References (Pattern)
For parent-child relationships, have children reference parents weakly:
package TreeNode;
func new(str $value) scalar {
my hash %self = {
"value" => $value,
"children" => [],
"parent" => undef # Don't store ref to parent
};
return bless(\%self, "TreeNode");
}
func add_child(scalar $self, scalar $child) void {
push($self->{"children"}, $child);
# Don't: $child->{"parent"} = $self; # Creates cycle!
}
Memory Profiling
Enable memory profiling to track allocations:
sys::memprof_enable();
# ... your code ...
sys::memprof_report(); # Print allocation stats
sys::memprof_reset(); # Clear counters
sys::memprof_disable();
C Interop Memory
When working with C code, you may need manual memory management:
# Allocate C memory
my scalar $ptr = c::alloc(1024);
# Use the memory...
c::write_int32($ptr, 42);
# Free when done
c::free($ptr);
See C Interoperability for more details on working with C memory.
Best Practices
- Let automatic cleanup handle most cases - don't over-optimize
- Use
undef()for large objects you're done with early - Pre-allocate arrays/hashes when you know the size
- Use StringBuilder for building large strings
- Implement DESTROY for objects that hold external resources
- Avoid circular references, or break them explicitly
- Use memory profiling to find leaks in long-running programs