Values, references, and pointer representation
E# presents two source-level storage disciplines. The compiler may select different CLR representations to implement a source feature, but that selection shall not change the observable rules in this section.
Value and reference discipline
Section titled “Value and reference discipline”ValueType = primitive | "struct" | "union" | "enum" | TupleType .ReferenceType = "class" | "ref" "union" | InterfaceType | DelegateType | ArrayType .PointerType = "*" ValueType .Assignment, argument passing, return, and field/collection storage copy a value-type value. Each destination is independent storage. Assignment of a reference type copies a reference to one object, so mutation through either name observes that object’s current state. The language does not provide identity for a value itself; it does provide identity for a class or ref-union object.
struct Point { var x: int }class Counter { var n: int }
let a = Point { x: 1 }let b = ab.x = 2 // a.x is still 1
let first = Counter { n: 1 }let second = firstsecond.n = 2 // first.n is now 2let constrains whether a binding may be reassigned or mutated through; it does not turn a class into a
value. A let class reference remains an alias to a mutable object if that object’s members are mutable.
Equality, nullability, and boxing
Section titled “Equality, nullability, and boxing”Value equality is structural when the type supplies it (for example through derive equality) or follows the
CLR value-type implementation otherwise. Reference equality distinguishes object identity. A non-nullable
value type does not admit nil; optionality is written T? and sharing/addressability is written *T.
At a CLR boundary, putting a value type into object or an interface boxes a copy into an object. Reading it
back as the original value type unboxes another value. Boxing does not turn the source variable into a shared
mutable location. Code that needs a shared, mutable value location uses *T instead.
*T source semantics
Section titled “*T source semantics”*T is a nullable, aliasing pointer to a location holding T, not an unmanaged address and not an array-like
pointer. It is valid only where T has value discipline; *Class is ill-formed because a class already has
reference identity. Member access through a non-null pointer automatically dereferences its target.
struct Node { value: int next: *Node }
let head = new Node { value: 1, next: nil }head.value = 2 // dereferences head's pointed-to storagenew T { ... } heap-allocates fresh storage for a value and yields *T. &place borrows or takes the
address of existing storage from an addressable place without allocating. Both forms preserve aliasing: two
pointers derived from the same storage observe the same later writes.
The distinction is about location identity, not whether a local happens to be physically stack-allocated.
var cell = Cell { ... } introduces a value location; &cell aliases or borrows that location. new Cell { ... }
heap-allocates a fresh location and yields its pointer. If an address-of local later escapes, the compiler
promotes the original location to durable storage rather than changing the source-level identity into a copied value.
The detailed allocation, promotion, and addressability rules are in
Pointer values and allocation.
Representation selection
Section titled “Representation selection”new T always uses a synthesized heap cell __Ptr_T. Escape analysis selects the carrier for an address
formed with &:
| Address-of use | permissible representation |
|---|---|
| confined to its frame and non-null | T&, directly aliasing storage |
| returned, captured, stored, or nullable | heap cell __Ptr_T |
The heap cell holds one shared T location. If an address-of local must escape, the implementation promotes
that local’s location so every pointer to it continues to observe the same location. Calls crossing between
the two internal representations receive conversions as required. This selection is not part of type identity:
source code only observes *T semantics.
Managed pointers are GC-tracked, cannot be used for pointer arithmetic, and cannot outlive a frame. The escape rule is what makes that guarantee compatible with first-class pointers. It is therefore invalid to infer from the generated representation that a source pointer can dangle or that it provides unsafe/native-pointer operations. If a compiler path leaves a managed alias at a durable boundary, it shall report ES2030 before IL emission rather than relying on a verifier failure.