Mako is a systems and backend language: clear to write, strict at compile time, fast at runtime, and designed so builds stay fast.
Product version: 0.2.1 (mako version → mako0.2.1).
Guided tour: The Mako Book.
Current syntax guide: GUIDE.md.
Low ceremony: ERGONOMICS.md.
Identity (our syntax): IDENTITY.md.
Keywords: KEYWORDS.md.
Product north star: VISION.md.
Honest matrix: STATUS.md.
Changelog: ../CHANGELOG.md.
| Pillar | How |
|---|---|
| Clear | Concise keywords, braces, local inference |
| Strict | Static types, no null, exhaustive match, Result / Option |
| Fast binaries | Native code via C (today) |
| Fast builds | Linear frontend; debug -O0 by default |
| Speed | As close to Rust as possible — no GC, native -O3 -flto (SPEED.md) |
| Concurrent (first-class) | crew / kick / join / channels / select / actor — structured, no orphans |
| Parallel (first-class) | fan — data-parallel map over cores; multi-kick crews |
| Memory | hold / share / arena — no GC |
| Safe by default | Bounds checks; unused Result is an error |
Mako is a unique language with unique syntax.
Not a Go dialect. Not a Rust dialect. Not a hybrid costume of either.
fn, on, pack, pull, hold, share, arena, crew, kick, …// Mako — preferred
fn handle(req: Request) -> Result[int, string] {
hold let body = req.body
arena a {
let msg = arena_text(a, body)
crew t {
let j = t.kick(process(msg))
return Ok(j.join())
}
}
}
on Point {
fn distance(self) -> int {
return self.x + self.y
}
}
| Mako-native (preferred) | Dual / compat sugar |
|---|---|
fn |
func |
let / let mut |
:= / var |
x: int |
x int |
-> int |
bare int after ) |
on Point { … } |
func (p Point) … |
struct Point |
type Point struct |
export |
Capitalized names |
crew / kick / join |
(no free go) |
hold / share / arena |
— |
Rule: docs, book, and mako fmt lead with the left column.
Dual forms stay for familiarity and migration — they do not define the brand.
Full identity checklist + %: IDENTITY.md (~86%).
Optional dual-form inventory: GO_SYNTAX_CHECKLIST.md.
User type parameters on functions, structs, and enums. All instantiations are monomorphized (one concrete C shape per args).
fn id[T](x: T) -> T { return x }
struct Pair[T] { a: T, b: T }
enum Box[T] { Val(T), Nothing }
fn describe_all[T: Describable](x: T) -> string {
return x.describe()
}
fn main() {
let p = Pair[int] { a: 1, b: 2 }
let q = id(p)
}
| Topic | Status |
|---|---|
| Generic functions | Done (also dual fn f<T>(…)) |
| Generic structs / enums | Done — write Pair[int] { … } |
| Nested monomorphs | Done — e.g. Box[Pair[int]] |
Interface bounds T: I |
Done — structural method set |
Iterator for via next |
Seed — by-value self does not auto-advance |
| Mutable lambda captures | Seed — heap cell when body assigns outer locals |
Full grammar: LANGUAGE_SPEC.md · tour: GUIDE.md §6 · book: ch03.
= is assignment only. Comparisons: == != < > <= >=.
Logical: && || ! (and and / or / not).
Bitwise: & | ^ &^ << >>, unary ^.
== / != work on strings (by content), named structs (field-wise), and
enums (tag + payload).
Byte strings with length. Prefer region ops when you only need to compare or search a span — no substring allocation:
| Builtin | Role |
|---|---|
str_eq / str_contains / str_index |
whole-string |
str_slice_eq / str_slice_ci_eq / str_slice_contains / str_slice_index |
s[off:off+len] without alloc |
str_at_eq / str_byte_at |
prefix-at-offset · single byte |
See BUILTINS.md § Strings and examples/testing/str_slice_zc_test.mko.
const / const fn)Integer const bindings and const fn bodies fold at compile time:
const fn clamp(x: int, lo: int, hi: int) -> int {
if x < lo { return lo } else {
if x > hi { return hi } else { return x }
}
}
const N = clamp(50, 0, 10) // 10
Supported in const: + - * / % bitwise, comparisons, && || !, let /
assign, return, if/else, if-expressions, match on ints (|, _,
bind), bounded while / for, C-style for, bare break /
continue, string seeds (const S = "…", +, str_len / ==), and
const fn string params/returns (plus int fns that use string locals).
Not a full CTFE interpreter.
actor Counter {
n: int = 0
receive Inc { self.n = self.n + 1 } // tag only
receive Add(delta) { self.n = self.n + delta } // int payload seed
receive Bye { let _ = 0 }
}
// Counter_spawn() · Counter_send(m, Counter_Add(5)) · Counter_loop(m)
Desugars to mailbox helpers; messages pack tag + optional int payload
(actor_pack / actor_msg_tag / actor_msg_payload). Bye/Stop stops the
loop. Tests: actor_test.mko.
interface Adder { fn add(int) -> int }
on Counter : Adder {
fn add(self, delta: int) -> int { return self.n + delta }
}
fn use(a: Adder, d: int) -> int { return a.add(d) }
Dyn values use a fat pointer + vtable. Also free-fn form Adder_Counter_add.
Tests: iface_*_test.mko, iface_on_iface_test.mko.
One monomorphized surface — no special collection package for everyday work. Codegen emits only the map monomorphs used in the unit (demand-driven), so large packs stay O(used shapes), not O(types²).
| Form | Notes |
|---|---|
[]T |
int/string/float/bool/byte/Struct/Enum; nested [][]T; bag slices []Option / []Result |
map[K]V |
K: int|string|float|bool|Struct|Enum · V: same, []T/[][]T, nested maps (depth ≤3), bags Option/Result (incl. nests), tuples (T,U[,…]) (incl. bag/chan fields), chan[T] |
| Ops | m[k], m[k]=v, has, delete, len, comma-ok, range, maps_* |
Short patterns (sets, groups, nested maps, bag values): ERGONOMICS.md.
Hands-on: howto/10-collections.md · book ch03.
Full guide: GUIDE.md §4b–4c · builtins: BUILTINS.md.
Always pack-qualified (clear call sites). Default name from optional
pack clause or path basename. Prefer as for aliases.
pull "strings"
pull "./lib.mko" // → lib.* if pack lib / path lib
pull "./util.mko" as helper // alias
pull _ "fmt" // blank: load only
pull . "./helpers.mko" // specialized: bare names
pull (
"path"
"fmt"
)
// dual: import / package still parse
// Types are pack-qualified too (same surface as calls):
// fn use(t: eng.Table) -> eng.Table
// let t = eng.Table { n: 0 }
// match t { eng.Table { n } => … }
// Enums: eng.Red / eng.Green(n) / eng.Color.Red / eng.Color.Green(n)
// Multi-return of pack structs: let t, n = eng.grow_pair(t0, 1)
// Maps: keys int|string|float|bool|Struct|Enum
// values: same | []T | map[K2]V (depth 2) — any combo
// e.g. map[string][]int, map[Point]int, map[Color][]string,
// map[string]map[string]int, map[string]bool
// Nested slices: [][]T (make / append / index / range)
Speed is the game; concurrent and parallel work is in the language, not a package:
// Concurrent — structured crew
crew t {
let j = t.kick(work())
print(j.join())
}
// Parallel — fan across cores
let out = fan items, fn(x) { heavy(x) }
Ordinary kicked jobs are joined with the crew. A blocked C/FFI call can delay
that join. An explicit detach must be paired with detached_join_all().
No async coloring. Details: SPEED.md.
| Tool | Role |
|---|---|
Scope / let |
Default |
arena |
Request/batch bulk free |
hold |
Unique / move |
share |
Shared read (RC seed) |
No null. Use Option[T] and Result[T, E]. Propagate with ?.
Discarding a Result is a compile error unless let _ = ….
GUIDE.md · IDENTITY.md · COMPAT.md · SECURITY.md · VISION.md