Skip to content

Programs & execution

This page specifies what a program is in E#: how source files compose into a single CLR assembly, how that assembly is laid out, where execution begins, and the order in which a program’s state is initialized. The syntax of each declaration is in Declarations; this page is about the unit above declarations — the assembly.

A compilation unit is one .es source file. Each begins with exactly one namespace declaration (Names & resolution) and then a sequence of usings and declarations:

SourceFile = NamespaceDecl { Using } { Declaration } .

A program (or library) is a set of compilation units compiled together into one assembly. Units do not import one another file-by-file; they share one namespace graph. A name in unit A is visible to unit B by the ordinary resolution rules — bare within the same namespace, imported or qualified across namespaces — regardless of which file declared it. usings, by contrast, are per-file: file A’s imports never leak into file B.

The compiler binds all units together in two passes — every type and signature across every file is registered before any body is bound — so declaration order and file order are irrelevant. A struct may reference a type declared later in the same file or in another file; forward references always resolve.

A compiled assembly contains, for each namespace, one public static partial class named after the namespace, plus a CLR type per declared struct / class / union / enum / interface / delegate func / static. The mapping is exact — see CLR mapping — but the two host-class rules are worth stating here because they govern where free functions and top-level state live:

  • Namespace host class. namespace Acme.Billing emits a public static partial class whose CLR name is the namespace’s last segment (a CLR type name cannot contain .), so namespace A.B.C produces module class A.B.C.C. Bare top-level free functions and namespace-scope const/let/var and namespace property accessors become static members of this class. Multiple files declaring the same namespace contribute to the one partial class.
  • static host class. static Foo { … } emits a second public static partial class in the same namespace — a sibling of the namespace class, not a nested type — holding its const, fields, and functions.

A function written with a receiver blockfunc (c: Circle) area() — is a method on that type instead of landing on the namespace class (Functions).

A program’s entry point is a free function named main. It is selected only when the assembly is built as an executable (a console output kind); in a library, a main is an ordinary function with no special status and the assembly has no entry point.

namespace App
func main() {
Console.WriteLine("hello")
}

main may be synchronous or asynchronous. An asynchronous main (its body contains await, so it returns ValueTask / ValueTask<T> per the uncolored-async rule) is wrapped by the compiler in a synchronous CLR entry shim that awaits it to completion (GetAwaiter().GetResult()), since the CLR entry point must be synchronous. The wrapper is generated; the source main is written exactly like any other async function.

KindResultEntry point
library (default)a .dll of types and methodsnone — main, if present, is ordinary
consolean executablethe free function main

The output kind is a project/CLI setting, not a source construct; the same sources produce a library or an executable depending on how they are built.

Within a run:

  1. Static / namespace stateconst folds to a compile-time literal and is inlined at each use (ES1011 if it does not fold). static fields (let / var) and namespace-scope state initialize per the CLR’s type-initialization rules — lazily, the first time the holding class is touched, in declaration order within that host.
  2. Namespace init. After that namespace host’s let / var fields initialize, its one optional init { ... } body runs. It runs once before the first namespace-host use, not at assembly load. Separate namespace hosts have no source-defined ordering; a call or state dependency triggers the other host through normal CLR first-use rules. An exception is cached by the CLR as type-initialization failure and later accesses fail accordingly.
  3. Value defaults before construction. A struct’s field defaults (field: T = expr) are applied when a value is constructed by composite literal or factory; a class’s field defaults run before its init body.
  4. Class init chaining. A class with init(args) : base(b) runs the base constructor first, then its own field defaults, then its init body (Inheritance).

Value struct has no constructor (ES3012); it is built field-by-field by a composite literal T { … }, the positional form struct T(a, b), or a factory function.

E# compiles .es source directly to a CLR assembly — .es → Mono.Cecil → .dll, over one typed intermediate representation. The compiler owns the language’s semantics, and every emitted assembly is required to pass ILVerify. Conformance is defined against it (Specification overview).

A project may also name assemblies the compiler loads and runs while compiling it — derives, analyzers, and templates (Compile-time providers):

<ItemGroup>
<EsharpProviderPack Include="path/to/MyRules.dll" />
</ItemGroup>

@(EsharpProviderPack) is a separate item group from @(Reference) on purpose: a reference is what the program compiles against, a pack is what the compiler executes. A pack changes what the compilation accepts — it can add derive names and claim region sigils — so it is a declared input, never inferred from a dependency. Each pack loads into its own collectible context; a load failure is reported per pack and does not stop the build.