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.
Stack-allocated spans
Section titled “Stack-allocated spans”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) * n → localloc → new 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.
Returning a span
Section titled “Returning a span”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 *Tparameter.
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.
The unmanaged generic bound
Section titled “The unmanaged generic bound”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 literals
Section titled “Byte-string literals”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.
Related surface
Section titled “Related surface”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.