By-ref calls and method sets
E# exposes the CLR’s by-reference family through three source forms. They all designate a location, but they differ in direction and mutability. This page specifies their syntax, call sites, and interaction with receiver methods.
Grammar
Section titled “Grammar”Param = ( [ "out" ] [ "readonly" ] identifier ":" Type | identifier ":" "*" Type ) [ "=" Expr ] .Arg = [ "out" | "&" | "*" ] Expr | identifier ":" Expr .AddressOf = "&" Expr .The complete type grammar admits readonly *T; it is the read-only borrow form. out x: T places out
before the name, while a normal pointer parameter is x: *T. The & and * call-site markers make
by-reference passage visible. An implementation shall reject a call whose marker and parameter direction
do not agree.
Parameter family
Section titled “Parameter family”| Source parameter | CLR shape | Contract | Typical call |
|---|---|---|---|
x: T | T | copied input | f(x) |
x: *T | T& or pointer cell | aliasing input/output location | f(&x) or f(*x) |
x: readonly *T | in T / [In] T& | zero-copy, no write through x | f(&x) |
out x: T | [Out] T& | callee supplies a result location | f(out x) / f(out var x) |
The ref, in, and out forms are the same CIL by-reference type; parameter metadata carries the
direction. A pointer parameter may use the managed-pointer or heap-cell representation from
Pointer values and allocation. At its source boundary it always
means an aliasing *T location.
Mutable and read-only borrows
Section titled “Mutable and read-only borrows”struct Vec { var x: int, var y: int }
func translate(v: *Vec, dx: int, dy: int) { v.x += dx v.y += dy}
func lengthSquared(v: readonly *Vec) -> int = v.x * v.x + v.y * v.yWriting through v in lengthSquared is ill-formed. The restriction is on that borrow, not on the
underlying variable: after the readonly call, a mutable borrow may still modify the original location.
readonly *T is a parameter form, distinct from readonly func (v: T), which is a read-only method
receiver.
An out parameter is a write-through result slot. Reads and assignments in the callee dereference that
slot automatically; a caller may provide an existing location or introduce one in the call:
func tryParse(text: string, out value: int) -> bool { value = int.Parse(text) return true}
func parse() -> int { if tryParse("42", out var value) { return value } return 0}The binding introduced by out var is in scope for the statement and what follows. out interoperates
directly with CLR out methods: no wrapper, tuple, or allocation is inserted.
Address-of and ref locals
Section titled “Address-of and ref locals”&name addresses an eligible local, parameter, field, or property location; it lowers to the matching
location instruction (ldloca, ldarga, ldflda, or a property location accessor). A bare typed local
(name: T = expression) is mutable but deliberately not eligible: use var name: T = expression for a
writable borrow or let name: T = expression for a readonly borrow. A local initialized from an address is a
ref local, and ordinary reads and writes through that binding dereference transparently.
A class receiver is never a *T target. &owner.property is therefore not a pointer to owner; it is valid
only for a property whose loca or mut contract
supplies a location. Class fields and computed properties cannot be addressed directly. A scoped mut
location is valid solely as the direct argument to its borrowing call and is rejected before any escape or
pointer-representation analysis if it is returned, stored, captured, or otherwise allowed to outlive resume.
A durable property ref local uses the binding keyword’s mutability: let location = &owner.property permits
reads, while var location = &owner.property may write through when the property location is writable. A
class-valued property location exposes the class value’s ordinary methods and members through that local, but
the local is still an opaque property location rather than *Class. Capture and async suspension raise it to
receiver-plus-protocol machinery; they never put a managed byref in a heap object or state-machine field.
The same prohibition is checked after generic substitution: a declaration containing *T cannot be
instantiated with a class type argument to manufacture *Class indirectly.
The binder first resolves &name as a function address. If name is a function, &name is a function
pointer; if it is storage, it is an address-of expression. The distinction comes from resolution, not a
second token. Function-pointer types and calls are specified in
Function values.
Pointer method sets and interfaces
Section titled “Pointer method sets and interfaces”*T has its own method set. A value receiver, func (v: T), is callable on both T and *T; a pointer
receiver, func (v: *T), is callable only on *T. The pointer receiver operates on the actual location:
struct Counter { var value: int }
func (c: *Counter) increment() { c.value += 1 }
func run() -> int { var counter = Counter { value: 0 } (&counter).increment() return counter.value}When only a pointer receiver satisfies a declared interface, the compiler creates a __Ptr_T wrapper that
implements that interface and forwards to the underlying hosts. The wrapper is generated only when *T
is used as that interface, avoiding an unconditional boxing cost. The source conformance remains nominal:
the type shall explicitly declare the interface.
See also
Section titled “See also”- Pointers & by-ref — the complete pointer reference.
- Methods and static facets — receiver kinds and method-only calls.
- Function values —
&functionandcallifunction pointers.