Skip to content

Memory model & value semantics

This page specifies the runtime behavior of values: when a value is copied versus shared, what aliasing is possible, how *T is represented, and the order in which expressions and initializers run. It is the operational companion to the Type system.

Every type is governed by one of two disciplines, fixed by its kind:

  • Value disciplinestruct, union, enum, primitives, tuples. A value is copied when assigned, passed, returned, or stored. There is no identity and no aliasing of the value itself; two value variables are independent storage.
  • Reference disciplineclass, ref union. A value is a reference to a heap object with identity; assigning, passing, or storing it copies the reference, and all holders observe the one object’s mutations.

The discipline is a property of the type, not of any annotation.

Under the value discipline a copy is made at each of:

let b = a // assignment copies a
f(a) // passing copies into the parameter
return a // returning copies out
xs.Add(a) // storing into a collection copies
p.field = a // storing into a field copies

Mutating the result never affects the source: after let b = a, writing b.x leaves a.x unchanged. A readonly struct (or a let-bound value) additionally forbids mutation of the binding, so no defensive copy is needed to protect the source. To mutate a caller’s value in place, pass it by reference as *T (Pointers) — then there is one shared location, not a copy.

Under the reference discipline none of these copies the object; they copy the reference. Two class names may denote the same object, and a write through one is seen through the other.

class and ref union have identity: two instances are distinct even with equal fields, and reference equality distinguishes them. Value types have no identity — equality is structural (with derive equality) or the CLR default, and nil is not a value of a plain struct (there is nothing for it to be the absence of). Optionality and sharing for value types are expressed with *T and T?.

*T is how a value struct (or a primitive) reaches shared, recursive, and nullable flows: a first-class, nullable, aliasing reference to a T location. It rests on the CLR’s one by-ref primitive, the managed pointer T&. Member access through a *T auto-dereferences.

The compiler chooses, per binding, by a whole-module escape analysis:

The pointer…RepresentationCost
escapes the frame (returned, stored in a field/collection, captured) or is nullable__Ptr_T — a heap reference cell shared by every holderone allocation, shared
provably does neithera managed pointer ref T — aliases the caller’s storage directlyzero allocation

Conversions between the two are inserted automatically where they meet at a call, and neither form is observable from source. A managed pointer is GC-tracked, cannot dangle, and cannot escape its frame — the escape analysis is precisely what guarantees the ref T form is only chosen when that is safe; an escaping pointer is the __Ptr_T cell, which the GC keeps alive as long as a holder exists. There is no unmanaged-pointer arithmetic and no way to form a dangling reference.

*Class is ill-formed (ES2003): a class is already a reference.

The only allocation expression is new T { … } / new T(…), which heap-allocates a value struct and yields a fresh *T (Pointers → new vs &). A bare composite literal T { … } builds a value in place (no heap allocation for the struct form). A class is allocated by calling its init. & never allocates — it takes the address of storage that already exists. Whether a given value struct lives on the stack or the heap is a consequence of how it is used.

Within an expression, operands evaluate left to right, and arguments evaluate left to right before the call. A side-effecting subexpression in a non-leftmost position is sequenced after the ones to its left. The short-circuiting operators && / and, || / or, ??, and ?. evaluate their right operand only when needed; the ternary c ? a : b evaluates exactly one of a / b. The ? propagation operator may short-circuit the enclosing function (returning the error) at the point it appears.

  1. A value struct’s field defaults (field: T = expr) apply when the value is constructed.
  2. A class’s field defaults run before its init body; with init(args) : base(b) the base constructor runs first, then this class’s field defaults, then its init body.
  3. const is a compile-time literal, inlined at each use. static and namespace-scope let/var state follow the CLR’s lazy type-initialization order (first touch, textual order within a class).

See Programs → initialization order.

defer { … } registers a block to run on scope exit, in LIFO order, lowering to a try/finally. It runs on every exit path — normal completion, return, break, or an exception unwinding through the scope — which is why it, not a finally keyword, is E#‘s cleanup mechanism (Statements).