MiloA memory-safe systems language with second-class references.
No lifetimes, no garbage collector, and every mutation visible at the call site.
No lifetimes, no garbage collector, and every mutation visible at the call site.
git clone https://github.com/milo-language/milo && cd milo && ./milo run examples/hello.milofn main() {
let name = "world"
print($"hello, {name}")
}fn main() {
let names: Vec<string> = ["milo"]
let team = names // `names` moves here. It is no longer yours
print(team) // ["milo"]
print(names) // error: use of moved variable 'names'
}fn total(xs: &Vec<i64>): i64 {
var sum = 0
for x in xs { sum += x }
return sum
}
fn double(xs: &mut Vec<i64>): void {
for i in 0..xs.len { xs[i] *= 2 }
}
fn main() {
var v: Vec<i64> = [1, 2, 3]
print(total(v)) // 6: borrowed for this call, no &v needed
double(&mut v) // the only kind of line that can change v
print(v) // [2, 4, 6]
}struct Parser {
src: &string, // error: references cannot be stored in structs
}
fn longest(a: &string, b: &string): &string { // error: cannot return a reference
if a.len > b.len { return a }
return b
}from "std/fetch" import { fetch }
from "std/runtime" import { Promise }
fn main() {
let a = Promise<i32>.run(() => fetch("https://example.com")!.status)
let b = Promise<i32>.run(() => fetch("https://httpbin.org/get")!.status)
print($"{a.await()!} {b.await()!}") // 200 200
}fn clamp(x: i64, lo: i64, hi: i64): i64
requires lo <= hi // the caller's obligation
ensures result >= lo && result <= hi // proven, for every input that meets it
{
if x < lo { return lo }
if x > hi { return hi }
return x
}A value is first-class if you can store it, return it and pass it around, and second-class if you can only pass it down into a call. In Milo, references are second-class: &T and &mut T exist only as function parameters. You can't return one, store one in a struct, or keep one past the call.
A reference that can outlive its call is the reason C has dangling pointers, Rust has lifetime annotations, and Java and Go have a garbage collector. One that only travels down the call stack can't outlive its owner, so Milo needs none of the three.
It also buys local reasoning: &mut x at a call site is the full blast radius of a mutation, and the checker settles every ownership question inside one function, never by a signature three modules away.
fn zeroNegatives(values: &mut Vec<i64>): void {
for i in 0..values.len {
if values[i] < 0 {
values[i] = 0 // in-place, no copy, no allocation
}
}
}
fn main(): void {
var v: Vec<i64> = [3, -1, 4, -5, 9]
zeroNegatives(&mut v) // v is mutated here and nowhere else
print(v) // [3, 0, 4, 0, 9]
}What you get: no lifetime annotations, no GC or RC, concurrency without Send/Sync, requires/ensures contracts checked by milo prove.
What you give up: returning or storing a view. Use an index, a Span, an owned copy, or an arena handle. The tie between a stored offset and its buffer becomes a named runtime check where Rust's lifetime is a compile error (why that trade).
| What you want | C | Rust | Milo |
|---|---|---|---|
| Return a pointer into a buffer you still hold | char *, you promise it stays valid | fn longest(...) -> &'a str | Not expressible. Return an index, a Span, or an owned string. |
| A parser that keeps the input | struct Parser { char *src; } | struct Parser<'a> { src: &'a str } | Own the input; store a cursor (pos: i64). |
| Iterator over a collection | pointer into the array | Iterator<Item = &T> | A cursor; each step takes the store: scanNext(&store, &mut cursor). |
| Graph, parent pointer, DOM | Node *next | Rc<RefCell<Node>> or an arena crate | std/arena: Arena<T> plus Handle<T>, checked at runtime. |
| Temporary read / mutation in a call | pointer argument | &T / &mut T | &T auto-borrowed; &mut T written at the call: f(&mut x). |
| Two owners of one buffer | two pointers, good luck | lifetimes, or clone / Arc | .clone(), or seal it and share a read-only copy. |
Same memory-safety guarantees as Rust wherever both languages can express the program. 250k+ lines of Milo so far; nearly every unsafe block is the C boundary, and none exist because the ownership model said no (the full matrix).
Milo is young, still a puppy 🐶, but we dogfood it hard: every program below is real, and building them is how we find what needs fixing. It's ready for you to try today.
A neon shooting gallery on a midway: targets spring up across five decks, and you hit them with a tethered blaster.
⧉ copy run commandA street race through a city at night, in the rain: a 2.8 km circuit between walls of lit buildings.
⧉ copy run commandOne-button flapper. Milo the chihuahua flaps his ears past fire hydrants.
⧉ copy run commandA 2D momentum platformer. You are Milo, a chihuahua: three levels, hydrants for checkpoints, instant respawn.
⧉ copy run command
Glass, metal and a low sun at 1920×1080, 512 samples a pixel on every core. About two minutes, and pure Milo from ray to PNG.
⧉ copy run commandPosition-Based Dynamics — Verlet point masses woven by distance constraints, the method real cloth engines use. Grab a node and fling it.
⧉ copy run commandA collisionless Vlasov solver in (x, v) phase space — finite-volume MUSCL with Strang splitting, winding a Maxwellian into trapped-particle vortices.
⧉ copy run command