Skip to content

Evaluation and initialization

This section specifies the sequencing guarantees exposed by E# programs. It is deliberately narrower than a machine-level CPU memory model: atomicity and inter-thread synchronization are provided by the CLR/BCL APIs that a program chooses to use.

Call = Callee "(" [ Argument { "," Argument } ] ")" .
Conditional = Expression "?" Expression ":" Expression .
ShortCircuit = Expression ( "&&" | "and" | "||" | "or" | "??" | "?." ) Expression .

Operands and call arguments evaluate left to right. Every argument has completed evaluation before the callee body begins. Named arguments retain this evaluation order: they are associated with parameters by name, but are evaluated in the written left-to-right order before invocation. The language shall not reorder visible side effects across these boundaries.

&&/and, ||/or, ??, and ?. evaluate their right side only when their left side requires it. A conditional evaluates its condition and exactly one branch. Result propagation (?) may return from the enclosing function at its occurrence; deferred cleanup still runs on that exit path.

Value construction applies field defaults as the value is formed. For a class construction, the sequence is:

selected base constructor
field defaults of the constructed class
selected init body

For a class primary-constructor header, capture stores precede field defaults and the parameterless init { } epilogue follows them. Constructor delegation with : this(...) transfers responsibility for the base call and field defaults to the delegated constructor, then executes the delegating body.

Namespace state and static-facet fields initialize through CLR type initialization. When a namespace declares init { ... }, its host has an explicit .cctor, is not beforefieldinit, initializes state in declaration order, then executes the init body. It runs once at first host use; an exception follows normal CLR failed type-initialization semantics. See Namespace hosts for the source rules.

defer { body } registers body for execution when its lexical scope exits. Multiple registrations execute in LIFO order. Cleanup executes on fallthrough, return, loop transfer, error propagation, and exception unwinding. It lowers to the CLR’s try/finally mechanism, so cleanup is sequenced before control reaches the destination outside the scope.

func read() -> Result<string, string> {
let handle = openFile()
defer { handle.Close() }
return parse(handle)? // Close still runs on the early return
}

Copy/reference semantics do not by themselves make compound operations on shared state atomic. A class field, namespace var, or heap pointer can be shared; read-modify-write operations on it must use the appropriate BCL synchronization primitive when concurrently accessed. E# exposes the BCL directly, including Interlocked, Volatile, locks, concurrent collections, CancellationToken, Task, and channels.

Structured concurrency and chan<T> give ownership, completion, cancellation, and message-passing rules; they do not reinterpret arbitrary shared mutable fields as synchronized. Prefer passing values through channels or isolating mutation behind an explicit synchronization boundary. The language supplies no unsafe escape hatch that would weaken CLR GC tracking or allow unmanaged pointer arithmetic.