Skip to content

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.

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 = a
b.x = 2 // a.x is still 1
let first = Counter { n: 1 }
let second = first
second.n = 2 // first.n is now 2

let 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.

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 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 storage

new 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.

new T always uses a synthesized heap cell __Ptr_T. Escape analysis selects the carrier for an address formed with &:

Address-of usepermissible representation
confined to its frame and non-nullT&, directly aliasing storage
returned, captured, stored, or nullableheap 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.