Namespace hosts and initialization
Every E# source file belongs to a namespace. The compiler realizes each namespace as a host CLR type, which is the home of free functions, namespace state, and the optional namespace initializer. This section specifies the source contract; ordinary name lookup is specified in Names and resolution.
Grammar
Section titled “Grammar”SourceFile = NamespaceDecl { Using } { NamespaceMember } .NamespaceMember = NamespaceField | NamespaceProperty | NamespaceInit | Declaration .NamespaceField = [ Visibility ] identifier ":" Type [ "=" Expression ] .NamespaceProperty = [ Visibility ] ( "let" | "var" ) identifier ( [ ":" Type ] "=" Expression | ":" Type ( "=>" Expression | AccessorBlock ) ) .NamespaceInit = "init" Block .A bare namespace declaration is a direct mutable static field. Namespace let and var are static
properties, matching their representation at type-member scope. An annotation is optional for a property
initializer because the initializer supplies a type; an annotation is required when there is no initializer
from which to infer the declaration type.
Host membership and CLR shape
Section titled “Host membership and CLR shape”The following mappings are normative. The host’s CLR name is the namespace’s terminal name within that namespace; free functions are static methods on that type.
| E# declaration | host member | CLR representation |
|---|---|---|
x: T = e | mutable field | direct static field initialized in the host .cctor |
let x = e | stored read property with implicit readonly loca | static getter, construction-only backing storage, and location companion |
var x = e | stored mutable property with implicit writable loca | static getter/setter, backing storage, and location companion |
let x: T => e | computed value with no implicit location | static get_x method and PropertyDefinition; no storage |
let x: T { } | write-once property with implicit readonly loca | static getter, private backing storage, and location companion |
var x: T { } | mutable property with implicit writable loca | static getter/setter, private backing storage, and location companion |
var x: T { set(v) => e } | transformed property; &x requires explicit loca or mut acknowledgement | static getter and setter storing the result of e |
const x = e | compile-time value | literal metadata / use-site inlining |
func f(...) | free function | static method |
The pub, bare, and priv prefixes apply just as they do to type members. A property prefix controls its
accessor methods; its synthesized backing field remains private. The host is an implementation device, not a
separate source namespace: a free function reads x, not Host.x.
The host is a CLR static class, so it may also host extensions: an ext (s: T) { ... } block at namespace
scope emits its members onto the host, exactly as a standalone static class hosts them. See
Methods → the ext block.
namespace Service
let initialEnvironment = AppConfig.Environmentvar requestCount = 0generation: int = 1let description: string => initialEnvironment + ":" + requestCount.ToString()var threshold: int { set(v) => if v < 0 { 0 } else { v } }
func observe() -> string { requestCount += 1 threshold = -3 return description}The declaration currentEnv: string = AppConfig.Environment is also a valid local typed binding. The
position of : string after the name is E#‘s typed-binding syntax; it is not a C#-style declaration.
Namespace init
Section titled “Namespace init”A namespace has at most one init { ... } block across every source file compiled into that namespace. The
block is unmodified: it cannot carry visibility or attributes. A second block is ill-formed, as are pub init,
priv init, and an attributed initializer.
The direct body of namespace init is synchronous. return is not permitted. await, await for, and
async let are not permitted, nor is yield. A called function may of course perform its own normal work;
the restriction concerns the initializer body itself and keeps the CLR type initializer synchronous.
namespace Service
var starts = 0
init { starts += 1 registerServices()}
func startCount() -> int = startsOrdering and failure
Section titled “Ordering and failure”The compiler emits namespace state initialization followed by the init body in the host type initializer
(.cctor). The host is not beforefieldinit, so the CLR runs this sequence lazily and once before first use
of the host. A function declared later in the same file or another file in the same namespace is available to
the initializer; declaration order does not limit this lookup.
state initializer 1state initializer 2…namespace init bodyfirst host function/property access proceedsIf initialization throws, CLR type-initialization semantics apply: the triggering access observes a
TypeInitializationException, and the host is not successfully initialized afterward. This is intentionally
different from a CLR module initializer: loading the assembly alone does not run namespace init.
Assignment and accessors
Section titled “Assignment and accessors”A namespace let property and a computed let property are immutable after initialization. A stored let x: T { } property may be assigned from namespace init to establish its backing value, then is read-only.
var properties are assignable from free functions and asynchronous state machines; a bare x: T field is
also mutable but has no property accessors. All three representations remain host-static.
namespace Service
let status: string { }var score: int { set(v) => v * 2 }
init { status = "ready" }
func update() -> int { score = 5 return score // 10}The declaration keyword selects the representation: bare typed state is a field, while let and var are
properties. A suffix then customizes the selected property. No source static keyword is needed because
namespace members are already static by virtue of being on the host. See
Properties and object initialization for the shared
accessor and backing-field rules.