Mako is a systems and backend language: clear to write, strict at compile time, fast at runtime, and designed so builds stay fast.
Guided tour: The Mako Book.
Full syntax guide with verified .mko examples: GUIDE.md.
Reserved keywords (38, from lexer): KEYWORDS.md.
This page is the short design overview. Product north star: VISION.md.
Honest matrix: STATUS.md. How-tos: howto/.
| Pillar | How |
|---|---|
| Clear | Concise keywords, braces, local inference, trailing-expression returns |
| Strict | Static types, no null, exhaustive match, Result / Option |
| Fast binaries | Native code via C (today) or a future object backend |
| Fast builds | Linear frontend; debug -O0 by default; avoid whole-program analysis in the hot path |
| Concurrent | crew — structured scopes; jobs cannot outlive the crew |
| Parallel | fan — data-parallel map over cores |
| Memory | Ownership + arena; RC/manual later; no mandatory GC (optional GC is a later opt-in) |
| Safe by default | Bounds checks; unused Result is an error — see SECURITY.md |
Mako is its own language. Ideas can inspire, but syntax decisions should be judged by whether they make Mako clearer, safer, and easier to teach. The goal is a distinct Mako surface that backend developers recognize quickly and keep using comfortably.
Mako's surface should follow these rules:
Result, Option, arena, hold,
share, crew, actor, and unsafe are part of the language identity.mako fmt define the canonical look so codebases feel consistent.= is assignment only. Comparisons use == != < > <= >=.
Logical: && || ! (and word forms and/or/not) with short-circuit.
Bitwise: & | ^ &^ << >>, unary ^. See KEYWORDS.md · GUIDE.md §2c.
import "strings"
import "./lib.mko" as lib
import (
"path"
"fmt"
x "./other.mko"
)
Brace form import { "a"; "b" } is also accepted. mako fmt groups 2+ imports into import ( … ).
See GUIDE.md · KEYWORDS.md.
Everyday backend style — not academic:
fn handleCall(call: Call) -> Result {
if call.valid() {
return route(call)
}
return error("invalid call")
}
v0.1 note: programs today use Result[int, string], Ok / Err, and
error("...") as sugar for Err(...). Generics currently use [T]; the
vision prefers List<T>, Map<K,V>, Result<T,E> — see VISION.md.
Target: for typical backend service size, compile times should be fast enough for interactive edit-run loops.
v0.1 reality
-O0 -g; release uses -O3 -flto.mako build --time to see frontend vs backend cost.Design constraints (keep builds fast)
fn fib(n: int) -> int {
if n <= 1 { return n }
return fib(n - 1) + fib(n - 2)
}
enum Shape {
Circle(int),
Rect(int, int),
Point,
}
fn area(s: Shape) -> int {
match s {
Circle(r) => r * r,
Rect(w, h) => w * h,
Point => 0,
}
}
fn fetch_both(a: string, b: string) -> Result[string, string] {
crew t {
let fa = t.kick(fetch(a))
let fb = t.kick(fetch(b))
return Ok(fa.join()? + fb.join()?)
}
}
fn squares(xs: [int]) -> [int] {
fan(xs, |x| x * x)
}
crew name { ... } — Opens a scope. On exit, all kicked jobs are joined.name.kick(expr) — Schedules work; returns Job[T].job.join() — Waits for the job; use ? when the job yields Result.fan(collection, fn) — Parallel map across a thread pool.No orphan background work: if it was kicked inside a crew, it finishes with that crew.
Mako aims for easy lifetimes without a mandatory tracing GC and without
C-style malloc/free as the default.
| Tool | Role |
|---|---|
| Scope ownership | Values are released when their scope ends |
arena name { ... } |
Bump region for request/batch work; one free on exit |
hold T |
Unique/move with CFG NLL + labeled break/continue (src/types/nll.rs) |
share T |
Refcounted share_int / share_clone / share_drop + mid-scope NLL |
| Manual (roadmap) | Low-level systems escape hatch |
| Optional GC (later) | App-level opt-in only — never required for backends/systems |
arena a {
let s = arena_text(a, "body")
let xs = arena_ints(a, 64)
} // region freed here
Details: VISION.md · SECURITY.md.
Today: crew / kick / join / fan, channels, cancel.
Next: timeouts that are portable everywhere, async I/O — see VISION.md.
crew name { ... } — Opens a scope. On exit, all kicked jobs are joined.name.kick(expr) — Schedules work; returns Job[T].job.join() — Waits for the job; use ? when the job yields Result.job.join_timeout(ms) — Wait with a deadline (best-effort on macOS).crew.cancel() / crew.cancelled() — Stop starting new kicks; cooperative cancel flag.fan(collection, fn) — Parallel map across a thread pool.chan_new(cap) — Buffered chan[int]; .send(x), .recv(), .close().actor_spawn / actor_send / actor_recv / actor_stop
(mailbox = channel; owned state in the loop). Target: actor Session { receive … }.
Beachhead: session servers — see examples/actor.mko and VISION.md.tcp_listen / tcp_accept / tcp_close / tcp_write.let ch = chan_new(4)
crew t {
let p = t.kick(producer(ch, 5))
let c = t.kick(consumer(ch))
print_int(c.join())
}
No orphan background work: if it was kicked inside a crew, it finishes with that crew
(or is skipped after cancel).
Errors print to stderr as error: with file:line:col, the source line, a
^ caret, and often a help: hint. Try:
mako check path/to/bad.mko
Mako testing keeps the low-friction package workflow developers expect:
| Convention | Mako |
|---|---|
| Test file | foo_test.mko (same directory as code) |
| Test function | fn TestAdd() { ... } |
| Run tests | mako test [path] |
| Assertions | fail("msg"), assert, assert_eq, assert_eq_str |
// add.mko
fn add(a: int, b: int) -> int { return a + b }
// add_test.mko
fn TestAdd() {
assert_eq(add(2, 3), 5)
}
fn TestAddTable() {
let a = [1, 2]
let b = [1, 3]
let want = [2, 5]
for i in 2 {
assert_eq(add(a[i], b[i]), want[i])
}
}
mako test discovers *_test.mko, merges sibling .mko package files, compiles a
harness that runs each TestXxx, and continues after a failed assert.
Exit code is non-zero if any test failed. Legacy test_*.mko with main still runs.
Filter: mako test --run TestAdd or -r 'Test*'.
Subtests (seed): call t_run("case") before asserts; failures print TestXxx/case.
There is no null. Use Option[T] and Result[T, E]. Propagate with ?.
Handle with exhaustive match. Discarding a Result is a compile error
(unless you write let _ = ...).
error("message") builds a failure (Err) for short call sites.
match is exhaustive for Result, Option, bool, and user enums.
A trailing expression in a function body is an implicit return.
| Function | Purpose |
|---|---|
print / print_int |
stdout |
str_len / str_eq / str_contains |
strings |
int_to_string |
formatting |
len |
array or string length |
assert |
abort on false |
arena_text / arena_ints / arena_stamp |
allocate into an arena |
http_serve |
tiny HTTP/1.1 server (fixed body) |
http_echo |
HTTP/1.1 echo of method + path (JSON) |
chan_new / .send / .recv / .close |
int channels |
error |
construct Err from a string |
Web/TLS/DB vision: STDLIB.md. Security: SECURITY.md. Tooling / optional GC / channels: VISION.md.
.mko → lexer → parser → typecheck → C → clang → binary
\________ frontend (keep linear) ________/
| Mode | Flag | clang |
|---|---|---|
| Debug (default) | mako build / mako run |
-O0 -g |
| Release | mako build --release |
-O2 |