Memory
How Antril manages memory - the collector each project chooses, destructors, regions, references and the borrow checker, unsafe code and pointers, and fixed buffers.
On this page
Values (numbers, bool, value types, enums) live where they are declared and are copied. Objects of classes, strings and arrays live on the heap, and variables hold references to them. How heap memory is given back is chosen per project, with gc: in the BUILD chunk of its .queen file:
chunk BUILD:
gc: refcount
Memory modes
gc: |
How objects are freed | Destructors run |
|---|---|---|
default (or tracing) |
A tracing collector finds unreachable objects and frees them. | After the collector finds the object unreachable; not necessarily before the program ends. |
refcount |
Each object counts its references and is freed when the count reaches zero. Cycles are not freed: break them yourself (for example by setting a field to null). |
As soon as the last reference goes. |
arena |
Objects are freed together when the region that made them closes. |
When the region closes. |
manual |
You free each object with delete. |
On delete. |
custom |
Reserved for a collector the project writes, named with gc.collector. In this release System.Core has no interface for it, and the project is built with the tracing collector. |
When nothing says otherwise the mode is default. larva config gc <mode> changes the default for your machine (see The larva command).
The same source compiles in every mode. What changes is when objects are freed, and so when their destructors run. Libraries compiled for a different mode can be used from your project; strings and bytes cross between them through System.Exchange.
Destructors
A class can declare destroy(). It runs once, when the object stops existing; a derived class's destructor runs before its base's.
import System;
into namespace Mem
{
public class Resource
{
str name;
public:
new(str name)
{
this.name = name;
Console.WriteLine("open " + name);
}
destroy()
{
Console.WriteLine("close " + name);
}
}
Use()
{
Resource r = new Resource("a");
Console.WriteLine("using a");
}
Main()
{
Use();
Resource b = new Resource("b");
b = new Resource("c");
Console.WriteLine("end");
}
}
In a project with gc: refcount, each object is destroyed as soon as nothing refers to it:
larva run
open a
using a
close a
open b
open c
close b
end
close c
Manual memory
With gc: manual, delete obj; runs the destructor and frees the object. Freeing memory by hand is unsafe, so delete goes in unsafe code:
import System;
into namespace Mem
{
public class Resource
{
public:
destroy() { Console.WriteLine("closed"); }
}
Main()
{
Resource r = new Resource();
Console.WriteLine("using it");
unsafe { delete r; }
Console.WriteLine("end");
}
}
larva run
using it
closed
end
delete through a base class runs the destructor of the object's real class.
Regions
With gc: arena, a region { … } block frees every object made inside it when the block ends, however it ends (also by break or return):
import System;
into namespace Mem
{
public class Resource
{
str name;
public:
new(str name) { this.name = name; }
destroy() { Console.WriteLine("close " + name); }
}
Main()
{
region
{
Resource a = new Resource("a");
Resource b = new Resource("b");
Console.WriteLine("leaving the region");
}
Console.WriteLine("end");
}
}
larva run
leaving the region
close b
close a
end
An object made in a region cannot outlive it. Returning it, for example, is an error when the program is built:
larva build
larva: error[RegionEscape]: `return` holds a value from region r0 and is still used after region_exit(r0)
--> Mem.$Module.Escape bb1[4]
Objects made outside any region live until the program ends.
The collector
System.GC talks to the tracing collector: GC.Collect() asks for a collection, GC.CollectionCount() says how many ran, and GC.ObjectCount() how many objects are alive. In the other modes objects are freed without a collector.
References and the borrow checker
&T is a shared reference (read only, many at a time) and &mut T an exclusive one (read and write, one at a time). &x and &mut x borrow a variable, a parameter, a field or an element. Members, methods and indexing see through a reference.
public value Counter
{
public:
i32 n;
}
Bump(&mut Counter c)
{
c.n = c.n + 1;
}
Read(&Counter c) : i32 => c.n;
Main()
{
Counter c;
Bump(&mut c);
Bump(&mut c);
Console.WriteLine(Read(&c)); // 2
&mut Counter r = &mut c;
r.n = 7;
Console.WriteLine(c.n); // 7
}
References are checked by the borrow checker. A reference lives until its last use; while an exclusive borrow lives, nothing else may use or borrow the same place. A reference cannot be returned from the function whose local it points to, and cannot be a field of a class or a type argument.
The checker has three settings, chosen with borrowcheck: in BUILD, with --borrowcheck on larva build and larva run, or for your machine with larva config borrowcheck:
| Setting | What happens |
|---|---|
off |
The default. References are not available: &T and &x are errors that ask you to turn the checker on. |
warn |
Borrow errors are reported as warnings and the program is built. |
enforce |
Borrow errors stop the build. |
An example of what enforce rejects:
Counter c;
&mut Counter r = &mut c;
c.n = 1; // c is borrowed by r, which is used on the next line
r.n = 7;
larva build --check --borrowcheck enforce app.ant
app.ant(15,3): error[ConflictingBorrow]: cannot assign to `c.0` while it is borrowed (mutable borrow of `c` on line 14)
Unsafe code and pointers
Pointers are allowed only in unsafe code: an unsafe function, an unsafe type, or an unsafe { … } block.
| Syntax | What it does |
|---|---|
T*, void* |
A pointer to a number, enum, value or pointer (not to an object). |
&x |
The address of x. |
*p, p[i], p->field |
Reads and writes through the pointer. |
p + n, p - q, p < q |
Arithmetic in elements (bytes for void*), distance, comparison. |
func*<A> : R |
A pointer to a C function. |
import System;
unsafe Fill(i32* p, i32 count, i32 value)
{
loop (i32 i = 0..count)
p[i] = value;
}
Main()
{
i32[] data = new i32[4];
unsafe
{
Fill(&data[0], data.Length, 9);
i32* second = &data[0] + 1;
*second = 1;
}
Console.WriteLine(data[0] + data[1] + data[2] + data[3]);
}
larva run
28
Pointers are how Antril calls C libraries; see Native bindings.
A project can forbid unsafe code with unsafe: deny in BUILD: then any unsafe function, type or block, and any pointer type, in the project's files is an error. Libraries it uses are not affected.
Fixed buffers
fixed T[N] name; keeps an array of N elements inside a value or class, with no separate allocation. sizeof(T) gives a type's size in bytes.
A final class can also keep a variable-length array inside each object, as its last field: fixed T[] name;. Each constructor says how many elements the object has with : fixed(n). Object and elements are allocated together, in one piece. The field is read and written by index and has a Length, but it is not an array value: it cannot be assigned, passed or returned.
import System;
into namespace Low
{
public value Header
{
public:
u32 Tag;
fixed u8[8] Name;
}
public final class Samples
{
fixed i32[] items;
public:
new(i32 count) : fixed(count)
{
loop (i32 i = 0..count)
items[i] = i * i;
}
i32 Count { get => items.Length; }
At(i32 i) : i32 => items[i];
}
Main()
{
Header h;
h.Tag = 7;
h.Name[0] = 65;
Console.WriteLine(h.Tag + h.Name[0]);
Console.WriteLine(sizeof(Header));
Samples s = new Samples(5);
Console.WriteLine(s.Count + " " + s.At(4));
}
}
larva run
72
12
5 16
Arrays
T[] is an array whose length is fixed when it is made. Indexes are checked: an index out of range stops the program (larva panic: index 3 out of bounds for length 1, exit code 101).
i32[] zeros = new i32[10]; // ten zeros
str?[] names = new str?[3]; // three nulls
i32[] some = [1, 2, 3]; // a literal
i32[,] grid = new i32[3, 4]; // rectangular, up to 8 dimensions
grid[2, 3] = 23;
i32[][] rows = [[1, 2], [3]]; // an array of arrays
A new array starts zeroed, so its element type needs a zero value. For a class or str, use a nullable element type (new Shape?[n]), a literal, or a List<T>.
Next: Async and reactive code.
Edit this page on GitHub Report an issue