Skip to content

Low-level & unmanaged

This page specifies the systems tier: the four constructs that let E# express an allocation-free binary codec directly — stack buffers, span-returning readers, bulk unmanaged reinterpretation, and UTF-8 byte constants. Each rests on machinery already in the language (the by-ref-like safety rules, escape analysis, reified generics) rather than a new runtime concept. The narrower numeric and span foundation is in Numerics & performance; this page is the low-level cap on it.

StackAlloc = "stackalloc" Type "[" "]" "(" Expr ")" . // stackalloc byte[](8)

stackalloc T[](n) is a frame-local buffer yielding a Span<T> — or a ReadOnlySpan<T> when the context target-types one. It reuses the ordinary T[](n) array-construction form verbatim; the stackalloc keyword flips the allocation to the stack (localloc) and the result to a span. It is always a real stack allocation and never a pointer: there is no loca-adjacent naming, no representation-selection cleverness, and no silent heap fallback.

func writeLong(w: *SectionWriter, v: long) {
let scratch = stackalloc byte[](8) // localloc — no heap traffic
BinaryPrimitives.WriteInt64LittleEndian(scratch, v)
w.buf.Write(scratch)
}

A stackalloc span is subject to the identical by-ref-like constraints as any other span (Numerics → by-ref-like safety): it cannot be boxed (ES2234), stored in a field (ES2230), captured by a function literal (ES2233), or carried across an await (ES2232). A stackalloc span that would escape the frame — returned directly or after a slice — is ES2231, exactly as any frame-rooted span is; it is a compile error, never a silent heap promotion.

stackalloc is a contextual keyword, recognized only immediately before an element type name and [; it is an ordinary identifier everywhere else.

CLR lowering. sizeof(T) * nlocallocnew Span<T>(void*, int) (or the ReadOnlySpan<T> ctor). The count is spilled to a temp so a side-effecting size expression is evaluated once. No newarr is emitted, and the resulting localloc-backed span passes ILVerify.

A Span<T> / ReadOnlySpan<T> return is well-formed when the returned span provably derives from one of:

  • a heap array,
  • a field of the receiver, or
  • a Span<T> / array / readonly *T parameter.

Its lifetime is tied to that source — the backing is GC-rooted independently of the frame, so it cannot dangle. A span derived from a stackalloc buffer or the address of a local still returns ES2231. There is no lifetime syntax: the rule is inferred, the same stance escape analysis takes (E# refuses Rust-style annotations).

func (r: *SectionReader) take(n: int) -> ReadOnlySpan<byte> {
if n < 0 or r.pos + n > r.data.Length { throw ModelFileException("short read") }
let s = ReadOnlySpan<byte>(r.data)[r.pos..r.pos + n] // slice of the heap-array field
r.pos += n
return s // heap-rooted → returnable
}
func (r: *SectionReader) readInt() -> int =
BinaryPrimitives.ReadInt32LittleEndian(r.take(4))

This relaxes the former blanket rejection of by-ref-like returns — the reader’s entire factoring depends on it — while keeping frame-rooted spans rejected.

Generics = "<" TypeParam { "," TypeParam } ">" .
TypeParam = identifier [ ":" Bound ] .
Bound = "unmanaged" .

A type parameter may carry the unmanaged capability bound. It emits the CLR unmanaged constraint — the NotNullableValueType flag plus a System.ValueType modreq(UnmanagedType) constraint, byte-identical to C#‘s where T : unmanaged — so MemoryMarshal.AsBytes<T>, span reinterpretation, and Unsafe.SizeOf<T> type-check against T:

func writeArray<T: unmanaged>(w: *SectionWriter, v: ReadOnlySpan<T>) {
w.writeInt(v.Length)
if BitConverter.IsLittleEndian {
w.buf.Write(MemoryMarshal.AsBytes(v)) // the fast bulk path
return
}
for x in v { w.writeElementBE(x) }
}

unmanaged is the first capability bound; it occupies the same <T: …> slot the roadmap reserves for type-set bounds (<T: int | long | double>). It qualifies the otherwise-unconstrained type-parameter rule in Generics: a bare T stays opaque, but a T: unmanaged exposes the unmanaged operations. Instantiating an unmanaged-bound parameter with a managed type is rejected by the runtime’s constraint check.

byte_string_lit = "b" `"` { string_char | escape } `"` . // b"MFL1", b"\x00\x01"

A b"…" literal is a byte[] constant of the string’s UTF-8 bytes — the E# analogue of C#‘s "…"u8, without a suffix (E# source is UTF-8; the \xNN escapes already exist and contribute a raw byte). It is const-foldable:

const HEADER_MAGIC: byte[] = b"MFL1"
const TRAILER_MAGIC: byte[] = b"MFLE"

The result is byte[] rather than C#‘s ReadOnlySpan<byte>: an array is heap, foldable, and freely returnable, where a span constant would collide with the span-return lifetime rules above. See Lexical → literals for the escape grammar.

The lexical and type-ergonomic additions that round out the codec surface are specified on their home pages: hex/binary integer literals and byte-string literals in Lexical structure, open-ended range slicing in Expressions, the enum underlying-type annotation in Declarations → enum, named tuple elements in Type system, and per-accessor visibility in Declarations → field & property visibility.