Milo Roadmap
What ships today, what is being worked on, and what is planned.
Completed
Core Language
Primitive types, let/var bindings, if/else, while/for loops, functions, structs, enums with exhaustiveness-checked pattern matching, generics with monomorphization and type inference, move semantics with use-after-move detection, second-class references (&T/&mut T in params only), closures (including escaping/move closures), traits with static dispatch and @derive(Eq), operator overloading via traits, Go-style interfaces with structural typing and vtable dispatch, Heap<T>, Option<T>, Result<T,E> with !/?/?? and auto-From error conversion, let-else, string interpolation, bitwise operators, hex/binary literals, type casts, for-in over ranges/Vec/array/string/HashMap and any type with a next() method, slicing on Vec/array/string, pub/private visibility, @embedFile, and an HIR-based typed IR.
Type System & Safety
- Ownership: single-owner move semantics, compiler-tracked drops, no GC, no reference counting
- Null safety:
Option<T>— no null pointers in safe code - Race safety: structural
Send/Sync— the compiler rejects data races atspawn()boundaries - Overflow safety: compile-time range proof plus runtime traps on
+ - * -x— and shift-out-of-range, divide-by-zero,INT_MIN / -1— in every build mode, release included.--no-overflow-checksopts back into wrapping, andwrappingAdd/saturatingAdd/checkedAddname it per operation. Measured cost: 0–2% on float, parsing and allocation work, up to ~30% on tight loops over unconstrained integers (reproduce withsh benchmarks/run-overflow.sh) unsafeblocks: pointer work is quarantined behind a grep target, with an unused-unsafelint on by default- Borrow invalidation: ref-while-frozen, use-after-invalidate, and call-site exclusivity are compile errors
- Arena safety: identity and generation validation for
Arena<T>/Handle<T> - No implicit coercion: explicit
ascasts only - Ranged integers (L1+L2):
type Altitude = i32(0..50000)with range propagation through arithmetic
Contracts & Proving
requires/ensures/invarianton functions and loops, checked at runtime in debug builds (--contract-checksforces them on at any optimization level)milo provedischarges those obligations statically throughstd/smt— a solver written in Milo itself.--solver=z3swaps in Z3 for non-linear arithmetic;--emit-smtprints the SMT-LIB2 obligations- Loop invariants are proved by induction
- An unproven obligation is reported as unknown, never as proven
Safety Profiles, WCET, Bare Metal
milo safety --list/--safety=<profile>: DO-178C DAL A/B/C, ISO 26262 ASIL A–D, NASA Class A/B, IEC 61508 SIL 3/SIL 4milo wcet: OTAWA flow facts and loop cycle estimates- Bare-metal targets: Cortex-M0/M3/M4/M4F/M7 (with RP2040 and STM32 aliases), a
--heap-sizecap, and a working heap soVec/Stringrun on microcontrollers - Bare metal is integer-only: the freestanding link carries no compiler-rt, so float math and 64-bit division are refused with a diagnostic pointing at fixed-point. Integer-only is what keeps the WCET numbers clean
Concurrency
One model — green tasks — with a single OS-thread escape hatch. No async/await, no function coloring:
- Green tasks (
std/runtime): stackful coroutines (64KB guarded stacks; kqueue, epoll, or Win32 events), cooperative scheduling —Task.spawn()for fire-and-forget, transparent async I/O (stream.recv()/stream.send()auto-yield on EAGAIN) - Promises (
std/runtime):Promise<T>.run(),.await(),Promise.all(),Promise.race()— structured concurrency over green tasks - OS-thread escape hatch:
Promise<T>.blocking()runsSendclosures on a real thread for CPU-bound work or blocking FFI; the result returns through the same.await() - Synchronization (
std/sync):Channel<T>(bounded FIFO, multi-producer, blocking + non-blocking),WaitGroup,AtomicI64,AtomicBool;selectover fd, timer, channel, promise, and child-exit arms (std/select) - Go exit semantics: when
mainreturns the process exits and outstanding tasks are abandoned — wait explicitly, or drive withschedulerRunToCompletion() mainis itself a green task in any program that can reachspawn, so a blocking call inmainno longer starves the tasks that would satisfy it
Standard Library (83 modules)
I/O & system: io, fs, path, env, environ, args, process, signal, dl, sysinfo, mem, os, platform, term, pty, keys, ansi Networking: net, unix, fetch, tls, https, http, httpmw, ws, url Data: json, csv, base64, base32, hex, sqlite, arena, set, pool, png Compression: deflate, inflate, zip, zstd Crypto & auth: crypto, sha256, sha1, hmac, jwt, totp, checksum, xxhash Concurrency: runtime, sync, select, event Strings: string, fmt, strconv, unicode, regex, cstr Math & verification: math, random, sort, smt CLI: argparse, color, log Time: time, datetime, uuid Testing: testing
TLS clients verify certificates and bind hostnames, and std/https serves HTTPS over the same binding. JSON parsing is RFC 8259-strict, with a lenient JSONC mode and a streaming pull tokenizer.
Formats with credible competitors and clients that track someone else's release cycle live as packages rather than in std, so a fix ships the same day on its own tag instead of waiting for a compiler release. TOML moved out for exactly that reason.
C Interop
Extern calls are safe when their arguments coerce safely; extern type gives opaque foreign handles, and string.cstr() hands C a borrowed pointer with no unsafe. @cLayout and @cSig verify extern struct layouts and function signatures against the real system headers at build time, and variadic externs are checked against libc's true fixed-parameter count. Milo can also be consumed from C: emit-obj, build-lib for a static archive, and a generated C header for the pub surface.
Platforms
macOS and Linux are fully supported on both aarch64 and x86_64. Windows is a partial target: the core language, std/io, processes, hashing, ConPTY, plain TCP, and the non-socket green tiers run as native PEs and are verified in CI on windows-latest. Pipe readiness, a TLS backend, AES-GCM, and regex are the remaining tiers.
Developer Experience
- LSP server: diagnostics, hover, go-to-definition, completions, code lens, document and workspace symbols, code actions, signature help, inlay hints, references, rename, formatting
- VS Code extension: syntax highlighting plus the LSP client
- Formatter:
milo fmt, written in Milo - Package manager:
milo add/install/publishplusmilo tool install, with a lockfile, a git-based cache, and GitHub repositories as the registry — built into the onemilobinary. Published packages cover PostgreSQL, Redis, markdown, TOML, YAML, JSON-RPC, OpenGL and SDL. - Docs from source:
milo docgenerates reference markdown from doc-comments;milo apisearches the standard library - Test framework:
@expect:/@error:annotations and amilo testrunner over 688 fixtures, plus 331 that must fail to compile and 33 that must be proved - Debugging:
-gemits DWARF that composes with any optimization level - CI: build and test on macOS, Linux, and Windows, plus a release pipeline with static linking
- Playground: the compiler's JavaScript backend running in the browser
Self-Hosting
milo0 — the Milo compiler written in Milo, about 38k lines — compiles its own source to a byte-identical fixed point at the production -O2 level: stage1 == stage2 == stage3. 590 fixtures pass under the self-hosted compiler.
The fixed point was the deliverable, and it is banked. Replacing src/ with milo0 was measured against a rule written down before the census that would decide it, and the census came in well under the threshold — so milo0 is frozen as proof rather than carried as a second compiler. Milo is proven able to compile itself; it is not going to pay the cost of maintaining two front ends to say so twice.
In Progress
- Windows: overlapped IO for pipe readiness, a TLS backend, AES-GCM, and a regex engine
- linux-arm64 CI coverage: macOS-arm64, linux-x64 and Windows-x64 are tested on every push; linux-arm64 is not yet, and needs to be
Planned
Language
- A dynamic disjoint split of
&mut [T]— mutable slice parameters and literal-range disjoint windows both ship (two(v[0..2], v[2..4])is accepted, an overlapping pair is rejected). What remains is splitting into a runtime number of windows, which second-class references cannot return as a tuple and so wants a callback form with the disjointness discharged by the prover - Borrowed byte views — offset/length I/O, buffer interop, zero-copy protocol parsing
- Named enum-variant fields —
ForEach { varName: string, … }instead of long positional payloads - Tuple binding in for-in —
for (i, x) in vec.enumerate() - Combinators beyond
Vec—map/filter/each/enumerate/find/any/all/sumship, but are gated onVec, so&[T]and[T; N]get none of them. Lifting that gate is the work;foldis the one adapter genuinely missing - Error boxing — the
anyhow-style half of error conversion - Ranged integers L3 — branch narrowing: after
if x < 50,xis known to be(min..49)in the then-branch - Structured OS errors —
errnoplus syscall and path context - C ABI layout control — packed structs and alignment
- Const generic parameters — generics are type-only today
- MIR — a lower-level IR for optimization passes, after self-hosting
Standard Library
- Incremental JSON byte-feed for unbounded input, and a nicer builder for the write path
- A pure-Milo regex engine, which also drops a libc dependency on every platform
std/decimal— scaled fixed-point for financial math
Tooling
- Cross-compilation to hosted targets — bare-metal and Windows crosses work; other targets need sysroot handling
- Faster builds — LLVM is 95% of a self-host build; the levers are interned method dispatch, less struct churn, and eventually MIR
@benchannotations and amilo benchrunner- "The book" — tutorials beyond the reference
Safety
- Purity inference for safe overlap at call sites
- Debug reference counting for patterns static analysis cannot reach
unsafe fndeclarations and a--deny-unsafeflag
Not Planned
- Lazy iterator adapters — laziness pays off only through aggressive inlining and would pull associated types into the trait system. Eager stages stay.
take/skip/zipas adapters —take(n)andskip(n)are already spelledv[0..n]andv[n..v.len], which slicing gives zero-copy and in&mutform; an eager adapter would allocate and clone what a view hands back for free.ziphas no type to return, since there are no tuples. Revisitziponly if tuples land.- Dependent types and hand-written proof terms — Milo's lane is SMT-discharged contracts with no proof obligations to write by hand.
- OS threads as a public API —
Thread/Mutex/RwLockwere removed in favour of the green tier andPromise.blocking().