Skip to content

Pointers & by-ref

*T is E#‘s pointer: nullable, aliasing, and first-class (storable, returnable, shareable). It applies to value data (any size) and primitives (*int); *RefData is ill-formed (ES2003) — a ref data is already a reference. Member access through a *T auto-dereferences.

Representation is implementation-defined. A whole-module escape analysis chooses per binding:

ConditionRepresentation
escapes the frame (returned, stored, captured) or is nullable__Ptr_T — a heap reference cell shared by every holder
provably neithera managed pointer ref T — zero allocation, aliases the caller’s storage

Conversions between the two forms are inserted automatically where they meet at a call. Neither is observable from source.

NewExpr = "new" TypeName ( "{" [ FieldInitList ] "}" | "(" [ ArgList ] ")" ) .

new T { … } heap-allocates a value data and yields a fresh *T — the only allocation expression, and the only way to mint a fresh pointer. new on a non-data is ES2144. The deprecated &T { … } spelling warns ES2143. & otherwise only takes addresses: new allocates something that does not yet exist; & takes the address of something that does.

The CLR has one underlying by-ref primitive, the managed pointer T& (ByReferenceType). ref, out, and in are the same IL type, distinguished only by parameter metadata. E#‘s by-ref family maps onto it:

FormCLR emissionDirectionDerefCall site
x: TTin (by value)f(x)
x: *Tref T or __Ptr_Tin / outexplicitf(&x) / f(*x)
x: readonly *Tin T ([In])in (zero-copy)explicit, read-onlyf(&x)
out x: T[Out] T&outimplicitf(out x)

A *T parameter emits ref T; both &expr and *expr at the call site pass by reference. readonly *T is a zero-copy read-only borrow (in T) — assigning through it is a binder error, and such a first parameter is not promoted. out x: T is the C# out shape ([Out] T&): assignment writes through the slot, reads load through it, and it interoperates one-to-one with C# out. Definite assignment of out is a documented obligation, not yet a hard error.

&name yields a managed pointer to a local, parameter, or field (ldloca/ldarga/ldflda); if name resolves to a function it is instead a function pointer. A local initialized from &expr becomes a ref local (a ByReferenceType variable); reads and writes through it transparently dereference. Managed pointers are GC-tracked, cannot dangle, and cannot escape the frame.

*T has its own method set: value-receiver functions (func f(x: T)) are in both T’s and *T’s sets; pointer-receiver functions (func f(x: *T)) are in *T’s set only. When a declared interface is satisfied only by the pointer method set, the generated __Ptr_T wrapper implements the interface and forwards to the underlying functions — so *T flows through interface-typed parameters without repeatedly boxing the value. The wrapper is generated only when a *T is actually used as an interface.