Run Makoto in the browser. Not yet.
The playground arrives with self-hosting, when the compiler can build itself to WebAssembly. Until then, read the examples, copy them, and run them locally with mk run.
use io
fn main() {
io.println("hello from makoto")
}hello from makoto
//@ kind: run
// =============================================================================
// Makoto — Mix-match de memory-managers e transmissão de dados entre eles (§5)
// =============================================================================
// Objetivo (avaliação de DX): mostrar, num único programa executável, como o
// dado circula entre as estratégias de memória — gc (padrão), arena, none
// (manual) — e os verbos de fronteira: @promote, @transfer, @pin, @limit, free.
//
// Princípio-norte (§5): a estratégia é COLORLESS. O TIPO é `[*]int`/`Rec` em
// toda parte; o que muda é ONDE a memória vive, decidido por @mm no ESCOPO, não
// no tipo. Você "só paga pelo que invoca": sem anotação, é gc.
//
// Rode: mk run docs/examples/mm_mixmatch.mko
// =============================================================================
use io
use mm
decl Rec { a: int; b: int }
// -----------------------------------------------------------------------------
// (1) gc — o PADRÃO. Nenhuma anotação: o coletor cuida do ciclo de vida.
// Uma função gc pode simplesmente RETORNAR um valor gc; ele sobrevive à
// função porque o coletor o mantém vivo enquanto houver referência.
// -----------------------------------------------------------------------------
fn make_gc() -> [*]Rec {
var r: [*]Rec = mm.alloc(1) // aloca no heap gc (padrão)
r[0].a = 1
r[0].b = 2
return r // transmissão por RETURN: sai da fn, o gc o segura
}
// -----------------------------------------------------------------------------
// (2) none — manual. `@mm(none)` desliga o coletor NESTE escopo: você aloca e
// LIBERA explicitamente (free / mm.release). Se a memória precisa SAIR do
// escopo viva, você PROMOVE para o gc na fronteira (@promote(gc)) — a única
// forma sound de um valor manual "escapar" para o mundo coletado.
// -----------------------------------------------------------------------------
@mm(none) fn make_escaping() -> [*]Rec {
var tmp: [*]Rec = mm.alloc(1) // memória manual, local a esta fn
tmp[0].a = 10
tmp[0].b = 20
// @promote(gc): MOVE a alocação manual para o heap gc. `tmp` deixa de ser
// sua responsabilidade (não precisa free) e o resultado sobrevive ao return.
let escaped: [*]Rec = tmp @promote(gc)
return escaped
}
// -----------------------------------------------------------------------------
// (3) Um worker que roda em OUTRO processo. Heaps são ISOLADOS entre processos
// (§5/§8): o que cruza o `spawn` é COPIADO PROFUNDO por padrão, ou MOVIDO
// com @transfer (evita a cópia e invalida a origem). Aqui recebe um Rec por
// valor e devolve a soma por um canal.
// -----------------------------------------------------------------------------
fn sum_worker(out: Channel[<-int], p: Rec) {
out <- p.a + p.b
}
@mm(none) fn main() {
// ---- gc padrão: pega o valor retornado por uma fn gc ----
let g: [*]Rec = make_gc()
io.println("gc: {{ g[0].a + g[0].b }}") // 3
// ---- none → gc via @promote na fronteira do return ----
let promoted: [*]Rec = make_escaping()
io.println("promote: {{ promoted[0].a + promoted[0].b }}") // 30
// ---- arena: alocação por bump, liberada em BLOCO no fim do escopo. ----
// Ninguém libera célula por célula; o `}` do bloco recupera tudo de uma vez.
@mm(arena) {
let scratch: [*]int = mm.alloc(8)
loop i in 0..8 { scratch[i] = i * 3 }
var s := 0
loop i in 0..8 { s = s + scratch[i] }
io.println("arena: {{ s }}") // 84 (0+3+..+21)
} // <- arena inteira recuperada aqui; nada de free manual
// ---- none manual: aloca, usa, LIBERA você mesmo ----
// (estamos numa fn @mm(none): mm.alloc aqui é memória manual)
let buf: [*]int = mm.alloc(4)
loop i in 0..4 { buf[i] = i + 100 }
var bs := 0
loop i in 0..4 { bs = bs + buf[i] }
io.println("none: {{ bs }}") // 406
mm.release(buf) // devolve a memória manual
// ---- @pin: manter um objeto gc vivo através de uma tempestade de alocações ----
let keep: [*]int = mm.alloc(2) @promote(gc) // promovido pro gc p/ o coletor gerir
keep[0] = 1000
keep[1] = 337
@pin keep // o coletor não pode mover/recolher enquanto pinado
loop k in 0..500 {
let churn: [*]int = mm.alloc(4) @promote(gc)
churn[0] = k // lixo: vira candidato à coleta
}
io.println("pin: {{ keep[0] + keep[1] }}") // 1337 (sobreviveu à churn)
@unpin keep
// ---- transmissão CRUZANDO um processo: deep-copy (padrão) vs @transfer ----
let out: Channel[int] = Channel.new(1)
let p := Rec{a: 40; b: 2}
// @transfer: MOVE p para o processo filho (sem cópia profunda); p fica
// inválido aqui depois — o dado viaja com sua memória para o outro heap.
spawn sum_worker(out, p @transfer)
let r := -> out catch |e| { return }
io.println("spawn: {{ r }}") // 42
// ---- @limit: teto de alocação para um escopo/spawn (falha controlada se estourar) ----
@limit(1mb)
{
let bounded: [*]int = mm.alloc(16)
bounded[0] = 7
io.println("limit: {{ bounded[0] }}") // 7
}
}gc: 3 promote: 30 arena: 84 none: 406 pin: 1337 spawn: 42 limit: 7
fn parse_port(s: string) -> Result[u16, error{Empty, NotNumber, OutOfRange}] {
if s == "" { return Err(error.Empty) }
// ...
}
port := parse_port(input) catch |e| { return default_port() }A fragment from the docs, not a complete program. Wrap it in a main to run it.
spawn worker(data) catch |e| {
log("worker died: {{e}}")
}
reply := server <-> request timeout(5s) // sync request-reply, deadline mandatoryA fragment from the docs, not a complete program. Wrap it in a main to run it.
decl Point { x: int; y: int } // fields -> struct
decl Shape { Circle(int); Square } // variants -> enum
decl Drawable { fn draw() } // signatures -> interfaceA fragment from the docs, not a complete program. Wrap it in a main to run it.
decl Transaction @must_consume { conn: *Connection }
fn commit(t: Transaction @consume) { … }
fn rollback(t: Transaction @consume) { … }
fn handle(t: Transaction) {
if ok { commit(t) }
// ERROR: on the else path, t is never consumed
}This one is about the compiler refusing the program, and that check is designed, not implemented.
Run it locally, today.
Linux x86_64 and Go 1.26. The compiler vendors its own clang, so there is nothing else to install.
$ go build -o mk ./cmd/mk
$ mk run hi.mko
hello from makoto
$ mk run docs/examples/mm_mixmatch.mko
gc: 3