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.
Compilation units
Section titled “Compilation units”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.
Assembly layout
Section titled “Assembly layout”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.Billingemits apublic static partial classwhose CLR name is the namespace’s last segment (a CLR type name cannot contain.), sonamespace A.B.Cproduces module classA.B.C.C. Bare top-level free functions and namespace-scopeconst/let/varand namespace property accessors become static members of this class. Multiple files declaring the same namespace contribute to the one partial class. statichost class.static Foo { … }emits a secondpublic static partial classin the same namespace — a sibling of the namespace class, not a nested type — holding itsconst, fields, and functions.
A function written with a receiver block — func (c: Circle) area() — is a method on that type
instead of landing on the namespace class (Functions).
The entry point
Section titled “The entry point”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 Appfunc 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.
Output kinds
Section titled “Output kinds”| Kind | Result | Entry point |
|---|---|---|
| library (default) | a .dll of types and methods | none — main, if present, is ordinary |
| console | an executable | the 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.
Initialization order
Section titled “Initialization order”Within a run:
- Static / namespace state —
constfolds to a compile-time literal and is inlined at each use (ES1011 if it does not fold).staticfields (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. - Namespace
init. After that namespace host’slet/varfields initialize, its one optionalinit { ... }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. - Value defaults before construction. A
struct’s field defaults (field: T = expr) are applied when a value is constructed by composite literal or factory; aclass’s field defaults run before itsinitbody. - Class
initchaining. Aclasswithinit(args) : base(b)runs the base constructor first, then its own field defaults, then itsinitbody (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.
The compiler
Section titled “The compiler”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).
Compile-time providers
Section titled “Compile-time providers”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.