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.
In this specification
Section titled “In this specification”- Values, references, and pointer representation
specifies copying, identity, boxing, and the representation-independent
*Tmodel. - Evaluation, initialization, and visibility specifies execution order, type initialization, deferred cleanup, and what the memory model does not guarantee.
Two disciplines
Section titled “Two disciplines”Every type is governed by one of two disciplines, fixed by its kind:
- Value discipline —
struct,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 discipline —
class,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.
Where copies happen
Section titled “Where copies happen”Under the value discipline a copy is made at each of:
let b = a // assignment copies af(a) // passing copies into the parameterreturn a // returning copies outxs.Add(a) // storing into a collection copiesp.field = a // storing into a field copiesMutating 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.
Identity
Section titled “Identity”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?.
The *T managed-pointer foundation
Section titled “The *T managed-pointer foundation”*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.
Two representations
Section titled “Two representations”The compiler chooses, per binding, by a whole-module escape analysis:
| The pointer… | Representation | Cost |
|---|---|---|
| escapes the frame (returned, stored in a field/collection, captured) or is nullable | __Ptr_T — a heap reference cell shared by every holder | one allocation, shared |
| provably does neither | a managed pointer ref T — aliases the caller’s storage directly | zero 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.
Allocation
Section titled “Allocation”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.
Evaluation order
Section titled “Evaluation order”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.
Initialization order
Section titled “Initialization order”- A value
struct’s field defaults (field: T = expr) apply when the value is constructed. - A
class’s field defaults run before itsinitbody; withinit(args) : base(b)the base constructor runs first, then this class’s field defaults, then itsinitbody. constis a compile-time literal, inlined at each use.staticand namespace-scopelet/varstate follow the CLR’s lazy type-initialization order (first touch, textual order within a class).
See Programs → initialization order.
Deferred cleanup
Section titled “Deferred cleanup”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).