Skip to content

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.

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.

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# declarationhost memberCLR representation
x: T = emutable fielddirect static field initialized in the host .cctor
let x = estored read property with implicit readonly locastatic getter, construction-only backing storage, and location companion
var x = estored mutable property with implicit writable locastatic getter/setter, backing storage, and location companion
let x: T => ecomputed value with no implicit locationstatic get_x method and PropertyDefinition; no storage
let x: T { }write-once property with implicit readonly locastatic getter, private backing storage, and location companion
var x: T { }mutable property with implicit writable locastatic getter/setter, private backing storage, and location companion
var x: T { set(v) => e }transformed property; &x requires explicit loca or mut acknowledgementstatic getter and setter storing the result of e
const x = ecompile-time valueliteral metadata / use-site inlining
func f(...)free functionstatic 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.Environment
var requestCount = 0
generation: int = 1
let 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.

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 = starts

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 1
state initializer 2
namespace init body
first host function/property access proceeds

If 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.

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.