Memory
CShift has no garbage collector. Values are copied; strings, arrays, closures and the blocks behind the standard
library’s containers are freed by automatic reference counting (ARC); everything else is memory that the program manages
itself, in unsafe code.
Values and references
Section titled “Values and references”A value type (the numbers, bool, char, enums, structs, unions, Fixed<T, N>, Optional<T>, Error<T>,
slices, pointers) is copied when it is assigned, passed by value, returned, stored in an array or captured by a lambda.
A struct is copied field by field: a field of a value type is copied along, a field of a reference type is shared.
A reference type (string, arrays, Action/Func with captures, SharedPtr<T>, Thread) is a pointer to a
block on the heap. A copy refers to the same block. The kinds of all types are listed in
types.
The containers of the standard library (List<T>, Dictionary<K, V>, StringBuilder, …) are structs whose fields
refer to arrays, so copies of a container share its contents:
var a = List<int>.Create();var b = a;b.Add(1); // a.Count() is 1 toovar c = a.Clone(); // an independent copyStrings cannot be changed, so sharing them is never visible (s += "x" makes a new string).
Reference counting
Section titled “Reference counting”Every block of a reference type has a count of the references to it. Storing a reference (in a variable, a field, an
array element, a captured copy) increments it, and overwriting or giving up a reference decrements it; the block, and
the references it holds itself, are released when the count reaches 0. A local variable gives up its references when
its scope ends, a temporary value at the end of the statement, a global variable after Main returns.
- Freeing is deterministic: it happens at these points, never later and never in the background.
- There is no cycle collection. Blocks that refer to each other in a cycle (a closure that captures the
container that holds it, for example) are never freed. Use indices into an array or a
Listinstead of references that point back. - The counts of strings, arrays and closures are not atomic; such values must not be shared between threads (the
compiler enforces this, see threads).
SharedPtr<T>,Thread,Mutex<T>have atomic counts. cshiftc --arc-statsprints the numbers of allocations and frees when the program ends.
ref and const ref
Section titled “ref and const ref”A parameter is passed in one of three ways:
| Parameter | At the call | In the function |
|---|---|---|
T x |
any value that converts to T |
a copy (for a reference type: another reference to the same block) |
ref T x |
ref v, where v is a variable, a field or an element of type T |
an alias of v: reading and assigning x reads and assigns v |
const ref T x |
any value that converts to T |
a read-only alias of the value (no copy); assigning to it or its fields is a compile error |
ref needs an exact type and a place that can be assigned: ref 3 and ref list.Get(0) are compile errors. A
ref argument stays an alias for the call only: there are no ref locals, fields or results, so an alias cannot
outlive the call. A method called on a const ref parameter (or on a field of one) runs on it directly; calling a
method that changes this there is a compile error, which names what the method does (call it on a copy, var copy = x;, or make the parameter ref). A method changes this when it assigns to a field (also to a field of an embedded
struct or to an element of a Fixed field), passes one with ref, takes its address, or calls a method that changes it
on this or on such a field; the compiler decides that from the method’s body. What a field refers to is not a part of
the struct: adding to a List field changes the list’s storage, not the struct, so p.Items.Add(x) and p.Items[i] = x are allowed. The same holds for a variable that a lambda uses (it gets a read-only copy). A local variable that is
initialized with a part of a const ref parameter (var tree = cg.Tree;) and only read afterwards is such an alias as
well: no copy is made.
An interface can be the type of a ref or const ref parameter (void Draw(const ref IShape s)): the function
takes any struct or union that implements it, without a copy, and calls the methods of the actual type. The function is
compiled once for each type it is called with (see generics).
unsafe
Section titled “unsafe”An unsafe context is the body of an unsafe function or method, an unsafe { } block or a statement with
unsafe before it (see statements). These operations are only allowed there:
- dereferencing a pointer:
*p,p->F,p[i]; - taking an address:
&x; - pointer arithmetic (
p + n,p - q) and casts between pointers and to or from integers; Memory.Allocate,Memory.Free,Memory.VolatileRead,Memory.VolatileWrite;s.CStr(),string.FromCStr(p),SharedPtr<T>.Ptr()and the other methods that hand out raw pointers.
Pointer types themselves, null and comparisons of pointers can be used anywhere, and so can calls of unsafe
functions and of C functions: the unsafe operations happen inside of them.
The compiler checks nothing about pointers: a pointer to a local variable that has gone out of scope, to a freed block
or to the inside of an array that was released is undefined behavior, like in C. &x of a local variable is valid as
long as the variable’s scope lasts; & of a field or an element points into the struct or array that holds it.
Manual memory. Memory.Allocate(n) returns a block of n bytes that is not cleared (C’s malloc), Memory.Free(p) frees
it; this memory is not counted. ARC values must not be stored in it (their counts would not be kept).
Volatile access. Memory.VolatileRead(p) and Memory.VolatileWrite(p, v) read and write a number, bool,
char, enum or pointer through p like a volatile access in C: each access happens, in the order of the program, and
none is merged with another or left out. This is what hardware registers and memory shared with interrupts need.
Threads
Section titled “Threads”A thread function runs on its own OS thread. It shares nothing with the thread that started it except through its parameters and its result, and the compiler checks that this sharing is safe:
- A thread function cannot use global variables, directly or through the functions it calls. Constants are fine.
- Its parameters must be transferable: numbers,
bool,char, enums;string(the thread gets its own copy);ReadOnlySlice<T>of transferable elements (the thread gets its own copy of the elements; arrays and slices convert to it);SharedPtr<T>,Mutex<T>andThread<T>of thread-safeT;Optional<T>,Error<T>and structs made of these. Not allowed:ref/const ref, pointers, arrays,Slice<T>,StringSlice, the containers of the standard library andAction/Func. - Thread-safe values (for
SharedPtr<T>and the value of aThread<T>that is passed on) are the transferable values without strings and slices: they are shared, not copied. - The result has no restriction: it only moves from the thread to the caller of
Join().
SharedPtr<T> gives several threads one value with an atomic count; access to the value through Ptr() from several
threads needs its own synchronization. Mutex<T> holds a value behind a lock: Lock() returns a MutexGuard<T> that
holds the lock until it is disposed (using), and the value goes in and out as a copy that shares no reference count
with anything (Memory.CopyForThread). The standard library’s threads API is in the
language guide.
There are no other shared variables between threads, so a program without unsafe code and C calls has no data races.