Skip to content

Comparisons

Makoto × C

makoto-vs-c.md · 4,260 lines · 65 min read

Mapeamento conceito a conceito da linguagem Makoto para C. Gerado de docs/mappings/_gen/ em 2026-10-05. Fonte de verdade do design: docs/english/*.md; sintaxe Makoto dos exemplos marcados verificada no compilador mk. 184 conceitos — implementado: 152 · parcial: 11 · planejado: 11 · não existe (por design): 10

Legenda: implementado · parcial · planejado · não existe (por design)

Outras comparações: JavaScript · TypeScript · Java · Rust · Stdlib · Índice

Antes da sintaxe: o que a Makoto (誠, “sinceridade”) recusa de propósito. Quem vem de C/JS/TS/Java/Rust tropeça mais nestes pontos do que em qualquer palavra-chave. Regra geral: o caso comum e seguro não leva marcação; só capacidade extra ou perigo é marcado (var, mut, unsafe, @mm(none)…). Cada sistema da linguagem (memória, concorrência, execução, tipos, FFI) carrega só as suas “cores” e não vaza pros outros.

Status: não existe (por design)

Não existe null. Ausência é tipada: Optional[T] com some(v) / none. Um ponteiro *T nunca é nulo; ponteiro que pode faltar é Optional[*T] (inclusive o NULL que vem do C).

Makoto

fn find(xs: []int, t: int) -> Optional[int] {
loop i in 0..xs.len { if xs[i] == t { return some(i) } }
return none
}

C

int find(const int *xs, int n, int t) {
for (int i = 0; i < n; i++) if (xs[i] == t) return i;
return -1; /* sentinela */
}

Diferença: C usa sentinelas (-1, NULL). Em Makoto o none é checado pelo compilador: não dá pra usar o valor sem tratar a ausência.

Status: não existe (por design)

Não há throw/try/catch de exceção. Falha é valor: Result[T, E] ou um conjunto de erros error{...}. Propagar é explícito (catch |e| { return e }) — não existe o operador ?. Falha irrecuperável é runtime.panic(...), que mata só o processo atual (o supervisor decide o que fazer).

Makoto

fn parse(s: string) -> Result[int, error{Empty}] {
if s == "" { return Err(error.Empty) }
return Ok(42)
}
v := parse(s) catch |e| { return e } // propaga explicitamente

C

int parse(const char *s, int *out) {
if (!*s) return -EINVAL; /* código de erro */
*out = 42; return 0;
}
if (parse(s, &v) < 0) return -1;

Diferença: C usa códigos de retorno + out-params (e errno). Makoto força o tratamento: ignorar um Result não compila.

Status: não existe (por design)

Não há class, extends, this implícito nem herança. Dados ficam em decl (struct); comportamento em funções com receiver explícito (fn (p: Point) norm()); reuso por composição (@embeds) e interfaces com satisfação estrutural (estilo Go).

Makoto

decl Point { x: int; y: int }
fn (p: Point) sum() -> int { return p.x + p.y }
p := Point{x: 1; y: 2}
io.println(p.sum())

C

typedef struct { int x, y; } Point;
int point_sum(Point p) { return p.x + p.y; }

Diferença: É o modelo do C: struct + função. O p.sum() da Makoto é só UFCS (açúcar de sum(p)).

Status: não existe (por design)

Não existe async, await, Future nem Promise. IO parece síncrono; o scheduler suspende só o processo que bloqueou. Paralelismo de IO = vários processos (spawn) + canais. Nenhuma função é “colorida”.

Makoto

fn fetch(ch: Channel[int]) -> int {
v := -> ch catch |e| { return 0 } // bloqueia só ESTE processo; os outros seguem
return v
}

C

/* read() bloqueia a thread; não-bloqueante exige epoll/io_uring manual */

Diferença: Em C você escolhe entre bloquear a thread ou montar um event loop. Makoto faz o event loop por baixo.

Status: não existe (por design)

Tipo do usuário não sobrecarrega + - * / (use método nomeado: a.add(b)). Duas funções soltas com o mesmo nome = erro “defined twice”. “Mesmo nome, tipos diferentes” é despacho explícito: fn name = { f1, f2 } (function set) ou match T dentro de um genérico. Mesmo nome com aridade diferente = parâmetro default ou variádico.

Makoto

fn int_s(i: int) -> string { return "int" }
fn bool_s(b: bool) -> string { return b ? "true" : "false" }
fn to_s = { int_s, bool_s } // roteador por tipo, resolvido em compile-time

C

_Generic((x), int: int_s, _Bool: bool_s)(x) /* C11 */

Diferença: _Generic é o análogo em C.

Status: não existe (por design)

Bloco não vale a última expressão. Para produzir valor ou sair: return, break ou panic. Exceções de glifo, que também são marcadores explícitos: o => do lambda de uma expressão e o ternário ?:. O => de um braço de match não entrega valor — é separador.

Makoto

fn double(x: int) -> int { return x * 2 } // `x * 2` sozinho não compila
v := match n { 0 => return "zero"; _ => return "outro" }

C

int dbl(int x) { return x * 2; }

Status: não existe (por design)

Todo binding nasce com um valor escrito. Não há int x; com lixo, nem zero-value do Go, nem undefined. 0, "" e List.new() só existem quando você escreve.

Makoto

var total := 0 // precisa do RHS
var xs: List[int] = List.new()

C

int total; /* local: lixo; global: 0 */

Diferença: Em Makoto não existe declaração sem valor.

Imutável é o padrão. := declara (tipo inferido), : T = declara com tipo, = sozinho só atribui (e só a var). Inteiros têm qualquer largura de 1 a 65535 bits e estouro dá trap; floats são sempre finitos (NaN/overflow = erro).

Status: implementado

x := v (atalho) ou let x := v: imutável em runtime. Tipo explícito estilo Odin: x: int = 5. Redeclarar o mesmo nome no mesmo escopo é erro (não há shadowing local; parâmetro pode ser sombreado no corpo).

Makoto

x := 5
let y: int = 10
x = 6 // ERRO: x não é var

C

const int x = 5;

Diferença: const do C é ‘read-only’, não ‘imutável por padrão’.

Status: implementado

var x := v é mutável. Um var que nunca é mutado é erro de compilação (use :=). Mutabilidade é do lugar (o binding), não do campo: p.x = 1 só vale se p for var.

Makoto

var n := 0
n = n + 1
n += 2

C

int n = 0; n += 3;

Status: implementado

const X := expr exige RHS conhecido em compile-time (pode chamar comptime fn). É o atalho de comptime let. comptime var é o “rascunho” mutável que só existe durante a compilação.

Makoto

const SIZE := 64
comptime fn fact(n: int) -> int { return n <= 1 ? 1 : n * fact(n - 1) }
const F := fact(5) // 120, calculado pelo compilador

C

#define SIZE 64
enum { F = 120 }; /* sem funções em compile-time */

Diferença: C só tem macros/enum; constexpr do C23 é limitado.

Status: implementado

Largar um valor de retorno no chão sem _ := é erro: descartar é uma intenção e intenção se escreve.

Makoto

_ := compute()

C

(void)compute();

Status: implementado

Tupla (a, b) agrupa valores. Desestruturar: q, r := f(). Troca: a, b = b, a. Função com vários retornos sem erro devolve tupla.

Makoto

fn divmod(a: int, b: int) -> (int, int) { return (a / b, a % b) }
q, r := divmod(7, 2)
var a := 1
var b := 2
a, b = b, a

C

typedef struct { int q, r; } DivMod;
DivMod d = divmod(7, 2);

Diferença: C não tem tupla: struct ad-hoc.

Status: implementado

Família paramétrica: i3, u7, u777, i65535… Comuns: i8..i128, u8..u128, isize/usize, int/uint (largura padrão), byte = u8, bit = u1. Aritmética checada contra a largura declarada.

Makoto

small: u3 = 7
b: byte = 255
big: u128 = 1

C

uint8_t b = 255;
struct { unsigned small : 3; } s; /* só em bitfield */

Diferença: C só tem larguras fixas (e bitfields).

Status: implementado

+ - * fazem panic se o resultado sai da faixa (constante que estoura = erro de compilação). Aritmética modular é pedida no operador: +%, -%, *%. Shift <</>> mascara pra largura (não dá trap).

Makoto

x: u8 = 250
y := x +% 10 // 4 (embrulha)
z := x + 10 // PANIC em runtime: estouro de u8

C

uint8_t y = x + 10; /* sem sinal: embrulha; com sinal: UB */

Diferença: Estouro com sinal em C é comportamento indefinido.

Status: implementado

Conversões que perdem informação (largura, floatint) são escritas com as. as é só de valor (nunca de ponteiro — pra ponteiro há mem.reinterpret, planejado).

Makoto

z := 300 as u8 // 44
i := 3.9 as i32 // 3

C

uint8_t z = (uint8_t)300;

Status: implementado

f16/f32/f64/f128/f256 + bf16; float = padrão. Uma operação que geraria NaN (0.0/0.0, √negativo) ou estouraria pra inf é erro de runtime — todo float é finito e totalmente ordenado (< é bool puro). Infinito direcionado de verdade é opt-in: ExtendedFloat.

Makoto

h: f16 = 1.5
q: f128 = 1.0
z := 0.0 / 0.0 // ERRO em runtime (NaN proibido)

C

double z = 0.0 / 0.0; /* NaN silencioso */

Diferença: Em C (e em quase todas) NaN se propaga em silêncio.

Precisão arbitrária: BigInt, Decimal, BigFloat, BigFloatArb

Section titled “Precisão arbitrária: BigInt, Decimal, BigFloat, BigFloatArb”

Status: implementado

Core-blessed (usam + - * /), mas vivem fora do escopo global. BigInt cresce sem estourar; Decimal/ Decimal[P, S] base-10 exato (dinheiro); BigFloat[M] com M bits de mantissa (MPFR); BigFloatArb[M] com cota de erro rigorosa (sem <, usa .cmp() ArbOrdering com Indeterminate). Literal toma o tipo do binding.

Makoto

a: Decimal = 0.10
b: Decimal = 0.20
io.println(a + b) // 0.30 exato
x: BigInt = 123456789012345678901234567890
io.println(x * x)

C

/* GMP / MPFR / libmpdec */
mpz_t x; mpz_init_set_str(x, "1234...", 10);

Diferença: C precisa de biblioteca e chamadas por função.

Caracteres: byte, codepoint, grapheme (char)

Section titled “Caracteres: byte, codepoint, grapheme (char)”

Status: implementado

Literal 'w' não tem tipo fixo: vira byte, codepoint (u32) ou grapheme pelo contexto. char é alias de grapheme (o caractere visual, pode ter vários codepoints). Sem contexto, qualifique: 'w'.codepoint.

Makoto

k: codepoint = 'w'
b: byte = 'w'
g: char = 'é'

C

char b = 'w'; /* um byte */
uint32_t k = U'w';

Diferença: Em C char é byte; em Makoto char é grafema.

Status: implementado

Duration é builtin (u64 nanossegundos, nunca negativo). Unidades no lexer, sem import: ns ms s m h d. Usado em timeout(...) e @supervisor(window: 10s). timeout(_) = sem prazo explícito.

Makoto

t := 300ms
reply := ch <-> req timeout(5s) catch |e| { return }

C

struct timespec t = { .tv_sec = 0, .tv_nsec = 300000000 };

Status: implementado

Notação de lexer convertida pra usize em bytes: 512b, 4kb/4kib, 16mb/16mib, 2gb/2gib, 8tb/8tib (base-10 vs base-2). Usado em @limit(16mb).

Makoto

n := 4kib // 4096
@limit(16mb)
spawn worker(data)

C

size_t n = 4 * 1024;

Status: implementado

alias UUID = u8 é sinônimo transparente (intercambiável). Tipo distinto é um decl envolvendo o valor.

Makoto

alias UUID = u8
u: UUID = 7
io.println(u + 1)

C

typedef uint8_t UUID;

Status: implementado

Tipos numéricos/strings têm ordem total (< > <= >= == != retornam bool). Ordering { Less; Equal; Greater } é builtin: resultado de .cmp(). Um tipo do usuário vira ordenável implementando cmp() -> Ordering (interface Comparable).

Makoto

o := 5.cmp(3) // Greater

C

int o = (a > b) - (a < b); /* -1/0/1 */

Status: implementado

decl Marker {} é o struct vazio (Unit: um único valor). decl Never { _ } é o enum sem variantes (inabitado). O “Top” (Any, interface{}, Object) não é escrevível: use um bound de interface. noreturn é o tipo de retorno de quem nunca volta (panic, loop infinito).

Makoto

decl Marker {}
decl Never { _ }
fn die(msg: string) -> noreturn { runtime.panic(msg) }

C

_Noreturn void die(const char *m);

Diferença: C não tem tipo vazio/inabitado.

Um loop para todos os laços, um match para switch/match/select, if é só statement (valor por condição = ternário). loop e match podem ser valor, entregando por return explícito.

Status: implementado

if não produz valor. Condição é bool estrito (sem truthiness).

Makoto

if n > 0 { io.println("pos") } else if n < 0 { io.println("neg") } else { io.println("zero") }

C

if (n > 0) puts("pos"); else if (n < 0) puts("neg"); else puts("zero");

Status: implementado

Escolha entre dois valores. Três ou mais = match.

Makoto

icon := liked ? "♥" : "♡"

C

const char *icon = liked ? "♥" : "♡";

loop unificado (infinito / while / for-each / range)

Section titled “loop unificado (infinito / while / for-each / range)”

Status: implementado

O cabeçalho decide: nada = infinito; expressão booleana = while; x in xs = for-each (coleção, gerador, range); 0..8 exclusivo, 0..=8 inclusivo.

Makoto

loop { break }
loop n > 0 { n -= 1 }
loop x in xs { io.println(x) }
loop i in 1..=3 { io.println(i) }

C

for (;;) break;
while (n > 0) n--;
for (int i = 1; i <= 3; i++) printf("%d\n", i);

Status: implementado

Três cláusulas separadas por ;, parênteses opcionais. O contador precisa ser var.

Makoto

loop var i := 0; i < 3; i = i + 1 { io.println(i) }

C

for (int i = 0; i < 3; i++) printf("%d\n", i);

Status: implementado

Label estilo Go antes do loop (outer:). break/continue nunca carregam valor.

Makoto

outer: loop i in 0..3 {
loop j in 0..3 { if j == 1 { continue outer }; if i == 2 { break outer } }
}

C

/* C: goto */ for (...) for (...) { if (i == 2) goto done; } done:;

Status: implementado

x := loop { ... return v }: o return dentro do loop-valor entrega ao binding (não sai da função). Todo caminho de saída precisa entregar.

Makoto

x := loop { return 42 }

C

int x; for (;;) { x = 42; break; }

Status: implementado

Sem fallthrough; | junta casos; _ é o default e é obrigatório em domínio aberto (int, string) e proibido em domínio fechado (enum) — adicionar variante quebra o build até tratar. Braços separados por quebra de linha (ou ; na mesma linha).

Makoto

match code {
200 | 201 | 204 => ok()
404 | 410 => gone()
_ => other()
}

C

switch (code) { case 200: case 201: case 204: ok(); break; case 404: case 410: gone(); break; default: other(); }

Status: implementado

Braço de match não entrega sozinho. Em posição de valor, cada braço faz return v (entrega ao binding); return match x { A => B } distribui o return pros braços.

Makoto

v := match n { 0 => return "zero"; _ => return "outro" }
fn area(s: Shape) -> float {
return match s { Circle(r) => 3.14 * r * r; Rect(w, h) => w * h }
}

C

const char *v = n == 0 ? "zero" : "outro";

Status: implementado

Roda na saída do escopo (inclusive return antecipado), vários em LIFO. Os argumentos são avaliados no defer; só a chamada é adiada. Também roda quando o processo morre e quando um gerador é abandonado.

Makoto

@mm(none)
fn work() {
let buf: [*]u8 = mm.alloc(64)
defer free(buf) // roda em qualquer caminho de saída
var n := 0
defer io.println("n={{n}}") // imprime n=0 (arg congelado)
n = 9
}

C

FILE *f = fopen("cfg", "r");
if (!f) return -1;
/* ... */
cleanup: fclose(f); /* goto cleanup */

Diferença: O padrão goto cleanup do C vira defer.

Status: implementado

panic não é global: vive em runtime (use runtime). Mata só o processo Makoto atual — os defers rodam, a memória do processo é liberada e o erro chega ao supervisor.

Makoto

use runtime
if bad { runtime.panic("estado inválido") }

C

abort(); /* derruba o programa inteiro */

Método = função com receiver explícito; x.f() é UFCS (= f(x)). Lambda só existe como argumento (não dá pra guardar em variável). Função aninhada é escopo, não closure. “Overload” é explícito (function set / match T).

Status: implementado

fn nome(p: T, ...) -> R { ... }. Sem retorno = sem ->. Modificadores ortogonais: pub, comptime, unsafe, @generator.

Makoto

fn add(a: int, b: int) -> int { return a + b }
pub fn greet(name: string) { io.println("oi {{name}}") }

C

int add(int a, int b) { return a + b; }

Status: implementado

fn f(x: int = 5): chamar f() usa o 5. É assim que “mesmo nome, aridade diferente” funciona.

Makoto

fn f(x: int = 5) -> int { return x * 2 }
f() // 10
f(1) // 2

C

/* sem default: macro ou duas funções */

Status: parcial

Parâmetro xs: ...int junta N argumentos (vira slice). Heterogêneo e tipado: ...Display. O spread no ponto de chamada f(...xs) está desenhado mas ainda não implementado (parser rejeita ... na chamada).

Makoto

fn sum(xs: ...int) -> int { var t := 0; loop x in xs { t += x }; return t }
sum(1, 2, 3, 4)
fn log(items: ...Display) { loop i in items { io.println(i.to_string()) } }

C

int sum(int n, ...) { va_list ap; va_start(ap, n); /* ... */ }

Status: não existe (por design)

f := fn(x) => ... e g: fn(int) -> int = double são erro. Quem guarda função é um parâmetro de tipo função (callback). Quer reusar? Dê nome (fn nome) e chame pelo nome.

Makoto

fn apply(f: fn(int) -> int, x: int) -> int { return f(x) }
fn double(n: int) -> int { return n * 2 }
apply(double, 5)

C

int apply(int (*f)(int), int x) { return f(x); }

Status: implementado

Duas formas, ambas só em posição de argumento: fn(x) => expr (o => entrega) e fn(x) { ...; return v }. Tipo de retorno inferido ou anotado (fn(x: int) -> int => x + 1). Captura por referência o escopo.

Makoto

use collections
ys := collections.map(xs, fn(n: int) => n * 10) // xs: List[int]
zs := collections.filter(xs, fn(n: int) { return n % 2 == 0 })

C

/* sem lambda: ponteiro de função + void* de contexto */

Diferença: C não tem closures.

Status: implementado

fn a() { fn b() {...} } é válido: b só é visível dentro de a, pode recursar e ser chamada antes da declaração — mas não enxerga os locais de a (tudo chega por parâmetro). Capturar escopo é exclusivo do lambda.

Makoto

fn outer(x: int) -> int {
fn helper(v: int) -> int { return v + 1 }
return helper(x)
}

C

/* C padrão não tem função aninhada (extensão GCC tem) */

Status: implementado

fn (p: Point) norm() (por valor) ou fn (p: *Point) move() (por ponteiro — acesso a campo exige deref explícito (*p).x, não há auto-deref). p.norm() é UFCS: igual a norm(p). Pipeline com . — não existe |>.

Makoto

decl Counter { n: int }
fn (c: Counter) get() -> int { return c.n }
fn (c: *Counter) inc() { (*c).n = (*c).n + 1 } // ponteiro NÃO auto-deref: `*` explícito

C

int counter_get(Counter c);
void counter_inc(Counter *c);

Função associada / construtor: fn (T) new()

Section titled “Função associada / construtor: fn (T) new()”

Status: implementado

Receiver sem nome = associada ao tipo (sem instância). Chamada por ponto: Arena.new(). Não existe static.

Makoto

decl Stack { n: int }
fn (Stack) new() -> Stack { return Stack{n: 0} }
s := Stack.new()

C

Stack stack_new(void) { return (Stack){0}; }

Status: implementado

Junta funções existentes num roteador por tipo, resolvido em compile-time (análogo ao proc{} do Odin). As assinaturas precisam ser distinguíveis; ambiguidade é erro na declaração do set.

Makoto

fn bool_s(b: bool) -> string { return b ? "true" : "false" }
fn int_s(i: int) -> string { return "int" }
fn to_s = { bool_s, int_s }
to_s(true); to_s(3)

C

#define to_s(x) _Generic((x), _Bool: bool_s, int: int_s)(x)

Status: implementado

Genérico com whitelist [T: {bool, int}] + match T no corpo: o compilador escolhe o braço e monomorfiza. Domínio fechado _ proibido.

Makoto

fn show[T: {bool, int}](x: T) -> string {
match T {
bool => return "B"
int => return "I"
}
}

C

/* _Generic */

Uma palavra só, decl, declara os três contratos — o corpo diz qual: campos nome: T = struct (produto), variantes Nome(...) = enum (soma), assinaturas fn ... = interface (comportamento). union fica de fora (é outro sistema: reinterpretação de bytes, unsafe).

Status: implementado

Produto. Campos separados por ; (ou quebra de linha). Literal nomeado P{x: 1; y: 2} ou posicional P{1; 2} — atenção: ; entre campos (vírgula é pra grupo de valores, como []int{1, 2}). O compilador reordena os campos pra reduzir padding (use @repr(c) pra layout fiel ao C).

Makoto

decl Vec2 { x: float; y: float }
a := Vec2{1.0; 2.0}
b := Vec2{x: 1.0; y: 2.0}

C

typedef struct { double x, y; } Vec2;
Vec2 b = { .x = 1.0, .y = 2.0 };

Diferença: C mantém a ordem declarada; Makoto não (sem @repr(c)).

Visibilidade de campo: privado por padrão, pub expõe

Section titled “Visibilidade de campo: privado por padrão, pub expõe”

Status: implementado

Código colabora (fn sem marca = visível no módulo); dados se protegem: campo sem marca é privado ao módulo, pub campo expõe pra outros módulos. Campo nunca tem var (mutabilidade é do lugar).

Makoto

decl User {
id: int // privado ao módulo
pub email: string // visível fora do módulo
}

C

/* sem visibilidade: struct opaco no .h + funções de acesso */

Diferença: O idioma C é o tipo opaco (typedef struct User User;).

Status: implementado

Cada variante pode carregar dados (como uma struct atrás de um rótulo). Construção com o tipo: Shape.Circle(2.0); no padrão o Shape. some. Exaustividade: match sobre enum cobre todas as variantes e proíbe _.

Makoto

decl Shape { Circle(float); Rect(float, float) }
s: Shape = Shape.Circle(2.0)
match s {
Circle(r) => io.println(r)
Rect(w, h) => io.println(w * h)
}

C

typedef enum { CIRCLE, RECT } Kind;
typedef struct { Kind k; union { double r; struct { double w, h; } rect; }; } Shape;
switch (s.k) { case CIRCLE: ...; case RECT: ...; }

Diferença: Tagged union manual; o compilador C não garante exaustividade nem acesso ao campo certo.

Status: implementado

Em posição de tipo, Nome{Variantes} é um subconjunto do enum: Shape{Circle} = “um Shape, garantidamente Circle”. Construir uma variante dá o tipo mais estreito, que alarga sozinho pro superconjunto. É a mesma mecânica dos conjuntos de erro error{...}.

Makoto

fn only_circle(s: Shape{Circle}) -> float { return 1.0 }
c := Shape.Circle(1.0) // tipo: Shape{Circle}
only_circle(c) // ok; um Shape.Rect não compilaria

C

/* sem equivalente: checagem em runtime do tag */

Status: implementado

Onde o tipo esperado é um enum conhecido, .Variante basta (inferido). Sem contexto, escreva completo (Color.Red).

Makoto

decl Color { Red; Green; Blue }
fn paint(c: Color) -> int { return match c { Red => 1; Green => 2; Blue => 3 } }
paint(.Green)
let c: Color = .Blue

C

typedef enum { RED, GREEN, BLUE } Color;
paint(GREEN);

Interface estrutural (satisfação implícita)

Section titled “Interface estrutural (satisfação implícita)”

Status: implementado

decl Nome { fn metodo() -> T } — só assinaturas, sem métodos default. Um tipo satisfaz implementando os métodos (estilo Go), sem implements. Comportamento derivado = função genérica livre sobre o bound.

Makoto

decl Shower { fn show() -> string }
decl P { x: int }
fn (p: P) show() -> string { return "P{{p.x}}" }
fn greet[T + Shower](v: T) -> string { return "hi " + v.show() }

C

typedef struct { const char *(*show)(void *self); } ShowerVT; /* vtable manual */

Diferença: Em C interface = struct de ponteiros de função.

Valor de interface (vtable) × bound genérico (monomorfização)

Section titled “Valor de interface (vtable) × bound genérico (monomorfização)”

Status: implementado

d: Shower = p guarda um fat pointer {dados, vtable} (tipo concreto apagado; lista heterogênea possível). [T + Shower] monomorfiza (chamada direta, sem vtable). Método genérico só via bound, nunca via valor de interface.

Makoto

d: Shower = p // dinâmico (vtable)
xs := []Display{p, 3, "s"} // heterogêneo e tipado
greet(p) // estático (monomorfizado)

C

ShowerObj d = { &p, &P_shower_vtable };

Status: não existe (por design)

Não existe Any/Object/interface{}/void* seguro. Você diz “qualquer tipo que faça X”: ...Display, [T + Serializable]. Interpolação {{x}} exige Display em compile-time.

Makoto

fn log(xs: ...Display) { loop x in xs { io.println(x.to_string()) } }
log(1, "a", true)

C

void log(void **xs, int n); /* void*: sem segurança */

Status: implementado

decl Display { fn to_string() -> string }. Primitivos já implementam; implemente pro seu tipo e ganhe interpolação {{p}} e qualquer genérico sobre Display.

Makoto

fn (p: P) to_string() -> string { return "P({{p.x}})" }
io.println("{{p}}") // P(1)

C

int p_to_string(P p, char *buf, size_t n);

Composição: @embeds(Tipo, methods|fields|all)

Section titled “Composição: @embeds(Tipo, methods|fields|all)”

Status: implementado

Sem herança: embutir um tipo e declarar o que é promovido. Promoção é transitiva, passa por campo ponteiro, e colisão entre irmãos é erro (resolve pelo caminho nomeado). b as A extrai a parte embutida por valor (tipagem nominal: B não vira A sozinho).

Makoto

decl A { v: int }
@embeds(A, all)
decl B { a: A; w: int }
fn take(value: A) -> int { return value.v }
b := B{a: A{v: 7}; w: 1}
io.println(b.v) // campo promovido
take(b as A) // extração explícita

C

typedef struct { A a; int w; } B; /* b.a.v: sem promoção */

Status: implementado

Struct/enum não pode conter a si mesmo por valor (tamanho infinito). A auto-referência passa por *T, [*]T, []T ou Optional[*T]. O compilador reporta o ciclo, nunca faz boxing escondido.

Makoto

decl Node { val: int; next: Optional[*Node] }

C

typedef struct Node { int val; struct Node *next; } Node;

Status: implementado

Grupo posicional de valores: (10, "x"). Desestruturação x, y := t. Retorno sem erro com vários valores.

Makoto

t := (1, "x")
x, y := t

C

struct { int a; const char *b; } t = { 1, "x" };

Status: implementado

Campos sobrepostos, sem discriminante (quem sabe o campo ativo é você). Construir e ler são operações unsafe com assume "motivo" (ou assert provável). Union com ponteiro não pode viver sob @mm(gc).

Makoto

unsafe union FloatBits { f: f32; bits: u32 }
fb := unsafe FloatBits{f: 1.5} assume "construir a union pra ler os bits"
y: u32 = unsafe fb.bits assume "reinterpretação intencional float->bits"

C

union { float f; uint32_t bits; } fb = { .f = 1.5f };
uint32_t y = fb.bits;

Status: implementado

@repr(c) desliga a reordenação e dá layout fiel ao C (obrigatório pra struct que cruza a fronteira FFI). @repr(packed) = bit-packed estilo Zig (cada campo com sua largura exata; u3+u5 num byte). Layout observável em compile-time: mem.size_of[T](), mem.align_of[T](), mem.offset_of[T]("campo").

Makoto

@repr(c)
decl CPoint { x: i32; y: i32 }
@repr(packed)
decl Flags { a: u3; b: u5 }

C

typedef struct { int32_t x, y; } CPoint;
struct Flags { unsigned a : 3; unsigned b : 5; };

Status: planejado

Desenhado (§17): @flags decl Perms { Read; Write; Exec } tornaria o enum int-backed e bitmaskável (Read | Write). Ainda não implementado (o checker rejeita o decorator). Hoje: constantes inteiras ou enum vindo do C (que já é bitmaskável).

Makoto

@flags decl Perms { Read; Write; Exec } // planejado
p := Perms.Read | Perms.Write

C

enum { READ = 1, WRITE = 2, EXEC = 4 };
int p = READ | WRITE;

Coleções são valores: bind copia (o compilador elide a cópia quando ninguém escreve). Compartilhar é pedido explicitamente: view xs[a..b] ou ponteiro. []T é slice (ponteiro + tamanho), [N]T array fixo, [*]T buffer cru sem tamanho. List[T] é builtin; Map/Set e as HOFs vêm de collections.

Status: implementado

Literal com vírgula: []int{1, 2, 3}, [3]int{...}, [_]int{...} (tamanho inferido). Indexação checada (panic fora dos limites). Tamanho é o campo .len.

Makoto

xs := []int{1, 2, 3}
arr := [3]int{1, 2, 3}
inf := [_]int{4, 5}
io.println(xs.len)

C

int arr[3] = {1, 2, 3}; /* sem slice: ponteiro + tamanho separados */

Status: implementado

var ys := xs é cópia independente; escrever em ys não muda xs. Construir struct com slice copia pro campo.

Makoto

xs := []int{1, 2, 3}
var ys := xs
ys[0] = 9 // xs continua [1, 2, 3]

C

memcpy(ys, xs, sizeof xs); /* cópia manual */

Status: implementado

.. = view O(1) (compartilha o backing; escrever pela view escreve no original). : = cópia O(n), independente. Mesma regra pra array, slice e string (em string com unidade: .bytes/.chars/.codepoints).

Makoto

var xs := []int{1, 2, 3}
var v := xs[..] // view do slice inteiro
v[0] = 9 // xs agora é [9, 2, 3]
c := xs[0:2] // cópia de 2 elementos

C

int *v = xs + 0; /* view = ponteiro */

Status: implementado

Builtin: List.new(), push, len(), at(i) (garante; panic fora) e get(i) ( Optional[T]). Nomes honestos: get = “talvez”, at = “eu garanto”.

Makoto

var xs: List[int] = List.new()
xs.push(1)
xs.push(2)
io.println(xs.at(0))
io.println(xs.get(9).or(-1))

C

/* sem vetor na libc: realloc manual */

Status: parcial

Implementado como tipos + funções livres em collections: map_new/map_put/map_get/map_has/map_size, set_new/set_add/set_has/set_size. Qualquer tipo com == serve de chave (struct incluída, sem declarar nada). Implementação atual: lista associativa (busca linear) — o hash-map do design, com hash derivado por reflexão, e a API em métodos (Map[K, V](), m.set(k, v), get_or_insert) ainda não existem.

Makoto

use collections
var m := collections.map_new[string, int]()
collections.map_put(mut m, "a", 1)
v := collections.map_get(m, "a").or(0)
var s := collections.set_new[int]()
collections.set_add(mut s, 3)

C

/* sem map na libc: hsearch(3) ou implementação própria */

Funções de alta ordem (collections.map/filter/reduce...)

Section titled “Funções de alta ordem (collections.map/filter/reduce...)”

Status: implementado

Sobre List[T]: map filter reduce any all count reverse find find_index index_of contains take drop concat flat_map. Use a forma qualificada collections.filter(xs, f) (o UFCS xs.filter(f) ainda falha no lower).

Makoto

use collections
ys := collections.map(xs, fn(n: int) => n * 10)
evens := collections.filter(xs, fn(n: int) => n % 2 == 0)
total := collections.reduce(xs, 0, fn(a: int, n: int) => a + n)

C

/* laço manual */

Status: implementado

Ausência tipada. Abrir com match (some(v) => / none =>), com default .or(d), ou abortar honestamente .or_panic() (o nome diz a consequência — não existe unwrap). .is_present() testa.

Makoto

match find(xs, 9) {
some(i) => io.println(i)
none => io.println("ausente")
}
v := find(xs, 5).or(-1)
w := find(xs, 5).or_panic()

C

int *p = lookup(k);
if (p) use(*p);

string é sempre UTF-8 válido (tipo builtin composto, não é []byte). Não existe s[i]: a unidade é sempre explícita (.bytes, .codepoints, .chars = grafemas). Comparação por operador é byte-a-byte; semântica/locale é por método.

Status: implementado

Bytes possivelmente inválidos são []byte. Validação na fronteira (string.from[u8](bytes) Result).

Makoto

s := "héllo"
io.println(s.bytes.len) // 6 bytes
io.println(s.codepoints.len) // 5 codepoints

C

const char *s = "héllo"; /* bytes NUL-terminados, sem garantia de UTF-8 */

Diferença: C string = bytes + \0; Makoto string = bytes + tamanho + invariante UTF-8.

Acesso por unidade: .bytes[i], .chars[i], .codepoints[i]

Section titled “Acesso por unidade: .bytes[i], .chars[i], .codepoints[i]”

Status: implementado

s[2] e s[2..6] puros são erro de compilação (“sobre O QUÊ?”). Cada acesso tem forma curta e método: s.bytes[2]/s.byte_at(2), s.chars[2..6]/s.chars_view(2, 6), s.codepoints[2:6]/s.codepoints_copy(2, 6).

Makoto

s := "héllo"
io.println(s.chars[1]) // é (grafema)
b := s.bytes[0] // byte
v := s.bytes[0..2] // view por bytes

C

unsigned char b = (unsigned char)s[0];

Status: implementado

Qualquer expressão dentro de {{ }}; formata via Display (checado em compile-time). \{{ é chave literal. Formatação por método ({{x.pad(8)}}, {{pi.fixed(2)}}, {{n.hex()}}) está desenhada, ainda não implementada.

Makoto

name := "Ana"
io.println("Olá {{name}}, {{ 2 + 3 }} itens")

C

printf("Olá %s, %d itens\n", name, 2 + 3);

Comparação: operadores = bytes; .equals(_, .semantic) = Unicode

Section titled “Comparação: operadores = bytes; .equals(_, .semantic) = Unicode”

Status: implementado

==/< são igualdade/ordem lexicográfica de bytes. Igualdade com normalização Unicode: a.equals(b, .semantic). Ordem alfabética por idioma: locale.Collator.new(.pt).order(a, b) Ordering. Não existe ===.

Makoto

a == b // bytes
"é".equals("é", .semantic) // NFC
use locale
c := locale.Collator.new(.en)
o := c.order("a", "b") // Ordering.Less

C

strcmp(a, b) == 0 /* locale: strcoll */

Status: implementado

Não há StringBuilder: var s + s.append(...) cresce dobrando o buffer. Concatenação com +.

Makoto

var s := "ab"
s.append("c")
t := s + "!"

C

char buf[64]; strcpy(buf, "ab"); strcat(buf, "c");

Conversões de encoding: to[u16], string.from[u16]

Section titled “Conversões de encoding: to[u16], string.from[u16]”

Status: implementado

UTF-8 sai direto de .bytes. Outros encodings são explícitos e alocam: s.to[u16]() []byte (LE, sem BOM); string.from[u8|u16|u32](bytes) Result. bytes.as_string_unchecked() pula a validação (unsafe). BOM/endianness: pacote unicode (swap16, with_bom16, strip_bom16…).

Makoto

e := "hé".to[u16]()
back := string.from[u16](e) catch |err| { return }

C

iconv(cd, &in, &inlen, &out, &outlen);

Status: parcial

s.to_cstring() dá bytes NUL-terminados (implementado). A volta, from_cstring(p) (char* Result[string] validando UTF-8), está desenhada mas não implementada. Hoje um char* vindo do C chega cru (*c.<mod>.char).

Makoto

v := unsafe c.stdlib.getenv("HOME".to_cstring()) assume "getenv com string válida"
match v { some(p) => io.println("definida"); none => io.println("ausente") }

C

const char *v = getenv("HOME");
if (v) puts(v);

Status: implementado

is_empty byte_len starts_with ends_with index_of contains repeat split join trim_start trim_end trim replace to_upper to_lower (escritos em Makoto, na stdlib).

Makoto

use strings
parts := strings.split("a,b,c", ",")
s := strings.join(parts, "-")
u := strings.to_upper("abc")

C

strtok / strstr / toupper (por char)

Dois eixos separados: Result[T, E] (deu certo ou não) e error{...} (quais falhas existem — conjunto fechado e estrutural). Trailers: catch |e| trata como unidade; match |e| ramifica por variante; match sobre o Result inteiro é o fork que recupera com valor.

Status: implementado

Enum builtin { Ok(T), Err(E) }. Sucesso é sempre explícito (return Ok(x)); erro propagado ganha açúcar (return e embrulha em Err).

Makoto

fn risky(n: int) -> Result[int, error{Neg, Zero}] {
if n < 0 { return Err(error.Neg) }
if n == 0 { return Err(error.Zero) }
return Ok(n)
}

C

int risky(int n, int *out); /* -1/-2 = erros */

Fork: match sobre o Result (recuperar com valor)

Section titled “Fork: match sobre o Result (recuperar com valor)”

Status: implementado

Cobre Ok e cada variante de erro, exaustivo (sem _ em conjunto fechado). É o lugar de recuperar com um fallback.

Makoto

match load(p) {
Ok(v) => io.println(v)
Err(error.NotFound) => io.println("não achou")
Err(error.Denied) => io.println("negado")
}

C

switch (rc) { case 0: ...; case -ENOENT: ...; case -EACCES: ...; }

Status: implementado

O Ok vai pro binding; o bloco trata o erro e precisa escapar (return, break, panic) quando há binding. return e propaga (vira Err(e)). Não existe ?.

Makoto

v := risky(x) catch |e| { return e } // propaga
w := risky(x) catch |e| { runtime.panic("falhou") } // aborta

C

if (risky(x, &v) < 0) return -1;

Trailer match |e| (ramificar por variante)

Section titled “Trailer match |e| (ramificar por variante)”

Status: implementado

Açúcar de catch |e| { match e {...} }. Só vale com união (≥2 variantes); cada braço escapa quando há binding.

Makoto

v := risky(n) match |e| {
Neg => { io.println("negativo"); return e }
Zero => return e
}

C

rc = risky(n, &v); if (rc == -1) {...} else if (rc == -2) {...}

Conjuntos de erro error{...} e união com spread

Section titled “Conjuntos de erro error{...} e união com spread”

Status: implementado

Estruturais e fechados; o estreito cabe no largo sem conversão. Compor = unir: error{open..., parse...} junta as variantes dos retornos de open e parse.

Makoto

fn load(p: string) -> Result[int, error{open..., parse...}] {
f := open(p) catch |e| { return e }
d := parse(f) catch |e| { return e }
return Ok(d)
}

C

/* códigos errno somados à mão */

Sucesso sem valor: retornar só o conjunto de erro

Section titled “Sucesso sem valor: retornar só o conjunto de erro”

Status: implementado

Não há unit/(): fn send() -> error{Full} — sucesso é a ausência de erro (return puro). Descartar sem tratar é erro; trate com catch (o sucesso segue pra próxima linha).

Makoto

fn send_it(ok: bool) -> error{Full} {
if !ok { return error.Full }
return
}
send_it(false) catch |e| { io.println("cheio") }

C

int send_it(bool ok) { return ok ? 0 : -EAGAIN; }

Status: implementado

Sem {...}, error é o domínio aberto (conjunto desconhecido): não-exaustivo, exige _/Err(e) genérico.

Makoto

fn ext() -> Result[int, error] { return Err(error.Whatever) }
match ext() { Ok(v) => io.println(v); Err(e) => io.println("erro externo") }

C

int ext(void); /* errno qualquer */

Status: implementado

Conjunto vazio (inabitado): Result[T, error{}] diz na assinatura que o Err é impossível — útil pra implementar interface falível com um tipo que nunca erra.

Makoto

fn never_fails() -> Result[int, error{}] { return Ok(1) }

C

/* sem equivalente */

Status: planejado

Desenhado (§7): error{HTTP500, PortError(int)} inline, aberto com match como qualquer variante. Ainda não parseia. Hoje: declare um decl enum com payload e use-o como E do Result.

Makoto

fn port(n: int) -> Result[int, error{HTTP500, PortError(int)}] // planejado

C

struct Err { int code; int port; };

Memória: gerenciamento sem cor, ponteiros, ownership

Section titled “Memória: gerenciamento sem cor, ponteiros, ownership”

Cada alocação carrega o seu MemoryManager (a estratégia viaja com o objeto), então gc, arena, none e c se misturam sem colorir assinaturas. @mm(...) define a estratégia padrão para novas alocações num contexto (processo, função, bloco, objeto). Sem borrow checker: um gate local decidível pega leak/double-free/ use-after-free no caminho manual, e @consume/@transfer movem ownership.

Status: implementado

Sem anotação, tudo é gerenciado pelo coletor (mark-sweep, raízes precisas via shadow stack). free(p) sob gc é no-op silencioso — o mesmo código roda em qualquer estratégia.

Makoto

xs := []int{1, 2, 3} // coletado automaticamente

C

int *xs = malloc(3 * sizeof *xs); /* ... */ free(xs);

Status: implementado

Estilo C, opt-in. mm.alloc(n) (ou mm.alloc(n, align)) dá um [*]T; free(p) devolve pelo manager do próprio objeto (o compilador preenche o tamanho).

Makoto

use mm
@mm(none)
fn main() {
let a: [*]int = mm.alloc(4)
a[0] = 7
free(a)
}

C

int *a = malloc(4 * sizeof *a);
a[0] = 7;
free(a);

Gate local de ownership (leak/double-free/UAF = erro de compilação)

Section titled “Gate local de ownership (leak/double-free/UAF = erro de compilação)”

Status: implementado

Onde alocação e free estão na mesma função e o ponteiro não escapa, o tempo de vida é decidível: esquecer o free num caminho, liberar duas vezes ou usar depois de liberar não compila. Sem anotação nenhuma.

Makoto

@mm(none)
fn main() {
let a: [*]int = mm.alloc(4)
io.println("sem free")
}
// erro: memory leak: "a" was allocated with @mm(none) and is never freed on some path

C

/* compila; valgrind/ASan acham em runtime */

Diferença: É o que o C deixa passar e sanitizers pegam depois.

Status: implementado

Bloco com uma arena anônima nova: nasce no {, morre no } (liberação em massa). Sub-arena = bloco aninhado. Operações de região ancoradas num ponteiro: mm.used(p), mm.reset(p), mm.release(p), mm.resize(p, n).

Makoto

use mm
fn handle() {
@mm(arena) {
let a: [*]int = mm.alloc(8)
io.println(mm.used(a))
} // tudo liberado aqui
}

C

/* arena manual: um malloc grande + bump pointer + free no fim */

Status: implementado

Mesma diretiva em escopos diferentes; objeto recebido mantém o manager de origem (“colorless”).

Makoto

@mm(arena)
spawn worker(data) // tudo que worker aloca vai pra uma arena
@mm(none)
fn low_level() { ... }

C

/* passar um allocator por parâmetro (padrão Zig) */

@consume: parâmetro linear (ownership atravessa a função)

Section titled “@consume: parâmetro linear (ownership atravessa a função)”

Status: implementado

Opt-in: o chamado passa a ser dono e precisa liberar/retornar/transferir exatamente uma vez; o chamador não pode usar depois. Guardar num campo de struct que escapa também quita a obrigação.

Makoto

fn free_it(p: [*]int @consume) { free(p) }

C

void free_it(int *p); /* "quem libera?" só na documentação */

@transfer: mover entre processos (invalida a origem)

Section titled “@transfer: mover entre processos (invalida a origem)”

Status: implementado

Valor enviado por canal/spawn é copiado profundamente por padrão (heaps isolados). @transfer move em vez de copiar: o objeto viaja com o seu manager e a variável de origem fica inválida (usar depois = erro). Ponteiro cru *T nunca cruza processo.

Makoto

r := Rec{amount: 84; tags: []int{1, 2}}
spawn worker(out, r @transfer)
ch <- b @transfer

C

/* passar ponteiro entre threads e prometer não usar mais (sem checagem) */

@promote(mm, shallow|deep): copiar pra outro manager

Section titled “@promote(mm, shallow|deep): copiar pra outro manager”

Status: implementado

Otimização (não correção): copia o objeto para outro MemoryManager — ex.: tirar da arena antes de enviá-la, ou trazer memória do C pro GC (p @promote(gc)). deep copia o subgrafo; a origem não é liberada sozinha.

Makoto

mine := p @promote(gc) // repatria memória vinda do C
chan <- result @promote(gc) @transfer

C

memcpy(dst, src, n);

@limit(16mb): teto de memória por processo

Section titled “@limit(16mb): teto de memória por processo”

Status: implementado

Estourar o teto mata o processo; o motivo (OOM) chega ao supervisor no catch |e|. OOM é evento de processo, não erro em cada push (isso colore tudo). Alocação com orçamento recuperável = descer pro mm.alloc.

Makoto

@limit(1kb)
spawn worker() catch |e| { io.println("morreu por limite") }

C

setrlimit(RLIMIT_AS, &rl); /* por processo do SO */

Status: implementado

Um só tipo de ponteiro (sem *const/*mut). Nasce de & sobre um lugar mutável com identidade no heap — um var de módulo, ou o elemento/apontado de outro ponteiro (esse não exige var no handle: o binding do ponteiro e a mutabilidade do apontado são independentes) — nunca de um local na pilha (pra mutar local use mut). Deref explícito em toda leitura e escrita (sem auto-deref). Ponteiro nunca é nulo: Optional[*T]. Sem aritmética com +.

Makoto

let a: [*]int = mm.alloc(4)
p := &a[1]
*p = 5
decl Node { val: int; next: Optional[*Node] }

C

int *p = &a[1];
*p = 5;

Status: implementado

N elementos contíguos que você possui, sem tamanho. Indexar é unchecked (unsafe); items[a..b] vira slice (onde o tamanho nasce); avançar a base é p.offset(k) — nunca p + k.

Makoto

b := a.offset(1)
x := unsafe b[0] assume "b aponta dentro do bloco de 4"

C

int *b = a + 1;
int x = b[0];

Status: implementado

Argumento vai por valor por padrão. mut no parâmetro e na chamada = a função altera a variável do chamador (que precisa ser var). Não é tipo, não escapa, é exclusivo (f(mut x, mut x) = erro); implementado por referência (zero cópia).

Makoto

fn bump(v: mut int) { v = v + 1 }
var x := 5
bump(mut x) // x == 6

C

void bump(int *v) { (*v)++; }
bump(&x);

Autofree: free inserido após o último uso

Section titled “Autofree: free inserido após o último uso”

Status: implementado

Onde a análise prova tempo de vida linear, o compilador insere o free logo após o último uso (tira o objeto do mundo do GC). Nunca muda o que o programa computa. Ligado por padrão; --no-autofree desliga; --autofree=default|conservative|optimized.

Makoto

// nada a escrever: é otimização do compilador
// mk build --autofree=optimized app.mko

C

/* manual */

Analisador de memória consultivo (mk analyze, -Wmm, --strict-mm)

Section titled “Analisador de memória consultivo (mk analyze, -Wmm, --strict-mm)”

Status: implementado

Um borrow-checker que você consulta: provado-fatal = erro; provável = aviso; poderia-ser-melhor = dica. --strict-mm promove avisos a erro; falso positivo é silenciado no local com assume "motivo".

Makoto

// mk analyze app.mko -> relatório (erros, avisos, dicas)
// mk build -Wmm app.mko -> liga os avisos
// strict-mm true (no mk.project)

C

// clang --analyze / -fsanitize=address

Opções do coletor (--gc-roots, --gc-collector, --gc=wasmgc)

Section titled “Opções do coletor (--gc-roots, --gc-collector, --gc=wasmgc)”

Status: parcial

--gc-roots=shadow (padrão, portável) ou stackmaps (LLVM statepoints, só host); --gc-collector=moving (compactador; objetos fixos com @pin); --gc=wasmgc usa o GC do host WebAssembly (trilha em andamento).

Makoto

// mk build --gc-roots=stackmaps --gc-collector=moving app.mko

C

n/a

A unidade de concorrência é o processo (estilo Erlang, compilado nativamente): heap privado, ciclo de vida gerenciado, falha contida. Comunicação só por canal tipado (não há memória compartilhada nem @shared). IO sem cor: o scheduler suspende só o processo que bloqueou.

Status: implementado

Cria um processo leve. Não devolve handle (referência é por registro, @register). Argumentos cruzam por cópia profunda (ou @transfer).

Makoto

spawn worker(data)

C

pthread_t t; pthread_create(&t, NULL, worker, data);

Status: implementado

Primitiva do core (sem literal, sem zero-value). Sem argumento = rendezvous (send bloqueia até o receive); com capacidade = buffer (send em buffer cheio bloqueia = backpressure). Buffer pertence ao runtime.

Makoto

c: Channel[int] = Channel.new()
b: Channel[int] = Channel.new(64)

C

int fds[2]; pipe(fds); /* ou fila + mutex + condvar */

Status: implementado

Perspectiva de quem segura: <-T = “eu envio T”, ->T = “eu recebo T”. Channel[T] = simétrico. O compilador checa que os dois lados são complementares no spawn.

Makoto

fn worker(ch: Channel[<-string, ->int]) { // envia string, recebe int
v := -> ch catch |e| { return }
ch <- "got {{v}}"
}

C

/* dois pipes, convenção por documentação */

Status: implementado

Receber devolve Result[T, error{ProcessDown}] (os remetentes morreram). Não existe close: o fim do stream é a morte do processo que alimenta. Entrega FIFO por canal.

Makoto

ch <- 42
v := -> ch catch |e| { return }
loop {
x := -> ch catch |e| { break } // produtor morreu: fim
process(x)
}

C

write(fd, &v, sizeof v);
read(fd, &v, sizeof v);

Request-reply síncrono <-> com timeout obrigatório

Section titled “Request-reply síncrono <-> com timeout obrigatório”

Status: implementado

ch <-> req timeout(5s) envia e espera a resposta. Sem timeout(...) = erro de compilação (dois <-> cruzados seriam deadlock garantido). Falha: Timeout | ProcessDown.

Makoto

reply := c <-> 21 timeout(1s) catch |e| { return }

C

/* write + poll(fd, 1000) + read */

Status: implementado

match sem sujeito espera vários canais. Trailer timeout(d) roda se nada chega; timeout(0) = poll não bloqueante. Com vários prontos, os braços se revezam (sem inanição e reprodutível — nem sempre o 1º, nem aleatório).

Makoto

match {
v := -> a => io.println(v)
v := -> b => io.println(v)
} timeout(100ms) {
io.println("nada chegou")
}

C

poll(fds, 2, 100);

Referência por nome: @register("nome") e grupos

Section titled “Referência por nome: @register("nome") e grupos”

Status: implementado

spawn não devolve pid. Para inspecionar depois, registre: nome único que sobrevive a restart; ou @register("handlers", group) pra endereçar por índice. Lookup: runtime.process("nome") ( Optional), runtime.group("nome"), runtime.self().

Makoto

@register("db_writer")
spawn db_writer(conn)
match runtime.process("db_writer") { some(h) => io.println("vivo"); none => io.println("sumiu") }

C

/* guardar o pid_t/pthread_t numa tabela global */

Scheduler: --scheduler=throughput|deterministic

Section titled “Scheduler: --scheduler=throughput|deterministic”

Status: implementado

Mesma API nos dois. throughput (padrão): work-stealing multi-core. deterministic: time-slicing rígido estilo BEAM (tempo real). Ambos preemptam (reduções em back-edges de loop + monitor de tempo). FFI bloqueante é absorvido no work-stealing; no determinístico, isole num processo.

Makoto

// mk build --scheduler=deterministic app.mko

C

// o scheduler é o do SO

Sem memória compartilhada entre processos

Section titled “Sem memória compartilhada entre processos”

Status: não existe (por design)

Não há @shared, mutex nem atomics de usuário. Dado compartilhado = um processo dono que serve pedidos via canal (padrão “actor”). Dentro de um processo não há aliasing concorrente, por isso não há borrow checker.

Makoto

fn counter(ch: Channel[->int, <-int]) {
var n := 0
loop { d := -> ch catch |e| { return }; n += d; ch <- n }
}

C

pthread_mutex_lock(&m); n += d; pthread_mutex_unlock(&m);

Modelo BEAM sem expor link/monitor crus: a árvore de supervisão é a estrutura do código. O catch |e| de um spawn é o monitor; morte de processo é um valor de erro. Ordem da morte: defers libera o @mm entrega o erro.

Status: implementado

O filho morre, quem fez spawn não morre e recebe o motivo. Sem anotação = one_for_one sem limite de restart.

Makoto

spawn worker(data) catch |e| { io.println("worker caiu") }

C

pid_t p = fork(); /* ... */ waitpid(p, &status, 0);

Status: implementado

O erro é estruturado: Crashed(msg), Normal, Timeout, Overrun, OutOfMemory…

Makoto

spawn crasher() match |e| {
Crashed(msg) => io.println("crashed: {{msg}}")
error.Normal | error.Timeout | error.Overrun | error.OutOfMemory => io.println("outro")
}

C

if (WIFSIGNALED(status)) ...; else if (WEXITSTATUS(status)) ...;

Status: implementado

Sem catch: o runtime reinicia sozinho, limitado pela janela. Com catch: você decide (runtime.supervisor.restart() / escalate(); não fazer nada = fica morto). Restart re-invoca com os mesmos argumentos — canal passado como argumento reconecta; nome registrado sobrevive.

Makoto

@supervisor(max_restarts: 3, window: 10s)
spawn worker(data) catch |e| {
runtime.supervisor.restart()
}

C

/* laço fork/waitpid + contador de restarts (ou systemd Restart=on-failure) */

Estratégias: one_for_one / one_for_all / rest_for_one

Section titled “Estratégias: one_for_one / one_for_all / rest_for_one”

Status: implementado

spawn individual = one_for_one; bloco spawn { a(); b() } catch |e| {...} = one_for_all (vivem e morrem juntos; o catch externo dispara quando o grupo esgota); @supervisor(strategy: rest_for_one) = reinicia quem veio depois (pipeline).

Makoto

spawn {
stage_a()
stage_b()
} catch |e| { io.println("grupo esgotado") }
@supervisor(strategy: rest_for_one, max_restarts: 5, window: 30s)
spawn { db_connection(); db_writer(); db_reader() } catch |e| { ... }

C

// manual

Status: implementado

Qualquer processo que faz spawn é supervisor implícito dos filhos. escalate() mata o atual e entrega o erro a quem o supervisiona; em main sem supervisor, o programa sai com o erro.

Makoto

spawn http_server(cfg) catch |e| { runtime.supervisor.restart() }

C

// manual

Não há “tipo canal de rede”: net.connect[T]/net.accept[T] devolvem um Channel[T] comum. A física aparece nos tipos que já existem: o payload precisa ser Serializable (derivado automaticamente por reflexão) e estabelecer a conexão devolve Result.

Status: implementado

Cada conexão aceita vira um Channel[T]; trate cada cliente como um processo.

Makoto

use net
listener := net.listen("0.0.0.0:8080") catch |e| { return }
loop {
conn := net.accept[Msg](listener) catch |e| { continue }
spawn handle(conn)
}

C

int s = socket(AF_INET, SOCK_STREAM, 0); bind(...); listen(s, 16);
int c = accept(s, NULL, NULL); /* + (de)serialização manual */

Status: implementado

Mesma API de canal: <-, ->, select, timeout.

Makoto

chan := net.connect[Msg]("127.0.0.1:8080") catch |e| { return }
chan <- request
reply := -> chan catch |e| { return }

C

connect(fd, (struct sockaddr *)&addr, sizeof addr); send(fd, buf, n, 0);

Generics e metaprogramação são a mesma coisa por baixo: comptime. [...] = parâmetros de compile-time (tipos e valores), (...) = de runtime. type é um valor de compile-time; decl Box[T] é açúcar pra uma comptime fn que devolve type. Restrições declaradas: + Interface (capacidade) e : {tipos} (whitelist).

Status: implementado

Monomorfizada por tipo concreto. Argumentos de tipo inferidos onde os valores fixam; senão explícitos. Tudo ou nada: não existe aplicação parcial (f[i32](p) pra f[T, V] é erro).

Makoto

fn id[T](x: T) -> T { return x }
id(5) // T = int (inferido)
id[int](5) // explícito

C

#define ID(x) (x) /* ou _Generic; sem generics de verdade */

Status: implementado

Box{5} infere T pelo campo; Box[int]{5} explícito. Enum genérico: Tree[int].Leaf(5) ou Tree.Leaf(5).

Makoto

decl Box[T] { v: T }
b := Box{5}
c := Box[int]{6}

C

#define BOX(T) struct { T v; }

Status: implementado

“T precisa saber fazer X”. Checado na fronteira da instanciação com erro limpo (não 3 camadas dentro).

Makoto

fn show[T + Display](x: T) -> string { return x.to_string() }

C

/* sem restrição: erro só onde quebra */

Status: implementado

“T só pode ser um destes” — de primeira classe (Rust precisaria de sealed trait). Combina com interface: [T: {i8, u16} + Display]. Forma plana: nada de |, & ou parênteses.

Makoto

fn small[T: {i8, u16}](x: T) -> T { return x }
fn both[T: {i8, u16} + Display](x: T) -> string { return x.to_string() }

C

_Generic((x), int8_t: f_i8, uint16_t: f_u16)(x)

Status: implementado

[...] aceita valores de compile-time, não só tipos: cada valor distinto gera uma especialização (monomorfização por valor). Base da camada prática de tipos dependentes (Vec[T, n]).

Makoto

fn zeros[N: int]() -> int { return N * 2 }
zeros[4]() // especializado com N = 4

C

#define ZEROS(N) ((N) * 2)

Status: implementado

Abstrair sobre o construtor não aplicado (List, Box) pra escrever map/collect uma vez. Cortes: só aridade 1 e 2, unificação de 1ª ordem, sem busca implícita de instância (resolvido por interface + monomorfização).

Makoto

decl Mappable[C[_]] { fn fmap[A, B](xs: C[A], f: fn(A) -> B) -> C[B] }
decl Box[T] { v: T }
fn (b: Box[T]) fmap[U](f: fn(T) -> U) -> Box[U] { return Box[U]{f(b.v)} }
fn pmap[C[_] + Mappable, A, B](c: C[A], f: fn(A) -> B) -> C[B] { return c.fmap(f) }

C

/* impossível */

Status: implementado

comptime fn roda inteira no compilador e some do binário (só em contexto de compile-time: const, [...], outra comptime fn). comptime var = rascunho mutável da compilação. Comptime é puro: sem IO, spawn, canais.

Makoto

comptime fn repeat(s: string, n: int) -> string {
comptime var acc := ""
comptime var i := 0
loop { if i == n { break }; acc = acc + s; i = i + 1 }
return acc
}
const R := repeat("ab", 3) // "ababab" embutido no binário

C

/* só macros do pré-processador */

comptime if / comptime match / comptime loop / comptime {}

Section titled “comptime if / comptime match / comptime loop / comptime {}”

Status: implementado

Resolvidos pelo compilador: só o ramo escolhido é emitido (compilação condicional). comptime loop desenrola (necessário quando o tipo varia por iteração, ex.: campos de struct).

Makoto

use arch
comptime if arch.current == .x86_64 { io.println("x86") }

C

#if defined(__x86_64__)
puts("x86");
#endif

Status: implementado

use compiler.{reflect, fail}. reflect(T).kind é um enum (Struct(s), Enum(e), Slice(elem), Array(elem, n), Optional(inner), Tuple(parts), Int(i), Float(f), Bool, String, Ptr…). s.fields com f.name, f.type, f.get(v); construir com reflect(T).construct(fn(f) => ...); fail("msg") aborta a compilação. Reflexão de tipo, não de valor em runtime (não existe any).

Makoto

use compiler.{reflect, fail}
fn count_fields[T](v: T) -> int {
var n := 0
comptime match reflect(T).kind {
Struct(s) => { comptime loop f in s.fields { n += 1 } }
_ => { }
}
return n
}

C

/* sem reflexão: X-macros */

Auto-derivação: serialize / deserialize / hash

Section titled “Auto-derivação: serialize / deserialize / hash”

Status: implementado

Sem macro nem anotação: a stdlib encoding deriva pela reflexão. Struct vira chave de Map/Set e payload de rede (Serializable) sem declarar nada. Tipo com invariante pode definir validate (checado no decode).

Makoto

use encoding.{serialize, deserialize, hash}
decl User { id: u64; name: string; admin: bool }
u := User{id: 42; name: "ana"; admin: true}
bytes := serialize(u)
back := deserialize[User](bytes) catch |e| { return }
let h: u64 = hash(u)

C

/* serialização escrita à mão por struct */

@generator fn + yield = máquina de estados stackless no mesmo processo (zero alocação por item). Pra suspensão arbitrária entre chamadas, use um processo. Abandonar um gerador roda os defers dele.

Status: implementado

yield só no nível do próprio gerador (não dentro de uma função que ele chama).

Makoto

@generator fn nat() -> int {
var i := 0
loop { yield i; i += 1 }
}
loop n in nat().take(5) { io.println(n) }

C

/* struct de estado + função next() manual */

Pipeline preguiçoso: .filter .map .take .collect

Section titled “Pipeline preguiçoso: .filter .map .take .collect”

Status: parcial

Cada estágio é outro Generator (sem alocação intermediária). Implementados: map, filter, take, collect. Desenhados e ainda não implementados: drop, reduce, enumerate, zip.

Makoto

fn even(n: int) -> bool { return n % 2 == 0 }
loop v in nat().filter(even).map(fn(n: int) => n * 10).take(3) { io.println(v) }
xs := upto(4).collect()

C

/* laços aninhados */

Dois níveis de contenção: o contrato na fronteira da função (@requires/@ensures) e o abate da operação perigosa no corpo (assert (cond) checa; assume "motivo" confia). unsafe não é região de permissão: é uma dívida que alguém paga explicitamente.

Status: implementado

Checada em cada chamada: estática onde prova, runtime (panic determinístico) onde não. Empilha (uma por linha; o compilador diz qual falhou). Predicado reutilizável = função booleana comum. Pré-condição checável torna a função segura.

Makoto

decl Buffer { data: []u8 }
@requires(i < buf.data.len)
@requires(i >= 0)
fn at(buf: Buffer, i: int) -> u8 { return buf.data[i] }

C

#include <assert.h>
uint8_t at(Buffer b, int i) { assert(i < b.len); return b.data[i]; }

Pós-condição: -> T == expr (terse) ou @ensures

Section titled “Pós-condição: -> T == expr (terse) ou @ensures”

Status: implementado

Terse no tipo de retorno (retorno único, o tipo é o slot). @ensures(...) pra várias condições/retornos; quando um tipo se repete, o retorno ganha nome (rótulo do contrato, não variável).

Makoto

fn at(buf: Buffer, i: int) -> u8 == buf.data[i] { return buf.data[i] }
@ensures(lo < hi)
fn split(a: int) -> (lo: int, hi: int) { return (a, a + 1) }

C

/* assert antes do return */

assert (cond) e assert (cond) else return E

Section titled “assert (cond) e assert (cond) else return E”

Status: implementado

Checa em runtime; falso = panic (troca UB por falha limpa). Com else return devolve um erro/valor em vez de panic.

Makoto

assert (x > 0)
fn parse(v: int) -> int {
assert (v >= 0) else return -1
return v
}

C

assert(x > 0); /* some com NDEBUG */

unsafe (expr, bloco, fn, union) + handlers

Section titled “unsafe (expr, bloco, fn, union) + handlers”

Status: implementado

Toda operação perigosa (deref cru, [*]T[i], union, chamada C, fn unsafe) exige um handler: assert (cond) (checa), assume "motivo" (confia; motivo não-vazio e conhecido em compile-time) ou passar adiante (unsafe fn). Função que abateu tudo no corpo é fn normal — o contágio para na primeira camada que prova.

Makoto

unsafe fn raw_at(xs: []int, i: int) -> int { return xs[i] }
v := unsafe raw_at(xs, 1) assert (1 < xs.len)
y := unsafe fb.bits assume "reinterpretação intencional"
r := unsafe {
a := raw_at(xs, 0)
return a * 2
} assume "índice 0 existe"

C

/* tudo é unsafe */

Predicado comptime fn em contrato (prova em compile-time)

Section titled “Predicado comptime fn em contrato (prova em compile-time)”

Status: implementado

Se os argumentos são conhecidos em compile-time, o predicado dobra: verdadeiro = contrato quitado sem custo; falso = erro de compilação. Com argumento de runtime vira checagem normal. Transforma assume em assert provado.

Makoto

comptime fn same_size[A, B]() -> bool { return mem.size_of[A]() == mem.size_of[B]() }
y: u32 = unsafe fb.bits assert (same_size[f32, u32]()) // provado pelo compilador

C

_Static_assert(sizeof(float) == sizeof(uint32_t), "size");

@cimport("header.h") roda o clang embutido, traduz o header inteiro e cria o submódulo c.<basename>. Tudo do C fica qualificado sob c. (inclusive tipos fundamentais, nomeados por ABI: c.systemv.int). Toda chamada C é unsafe e precisa de handler. Toolchain hermético (musl embutido, mk cc = cc drop-in).

Status: parcial

Funções, structs, typedefs, enums, macros objeto (literais) e função (via shim). as renomeia o submódulo. Lacunas atuais (registradas): não há como passar -D (ex.: _GNU_SOURCE — some unshare/clone/CLONE_* do musl); macros com expressão ((1<<18)) são descartadas; errno ainda não é exposto.

Makoto

@cimport("stdlib.h")
n := unsafe c.stdlib.atol("100kg".to_cstring()) assume "string válida"

C

#include <stdlib.h>
long n = atol("100kg");

Tipos C qualificados por ABI (c.systemv.int)

Section titled “Tipos C qualificados por ABI (c.systemv.int)”

Status: implementado

Não existe c_int solto: a largura depende da psABI. c.systemv.long = 64 bits, c.win64.long = 32; c.gcc.int/c.clang.int seguem o alvo. Nomes de um token só (unsigned long use u64).

Makoto

let n: c.systemv.int = 5
sz: c.systemv.size_t = 16

C

int n = 5; size_t sz = 16;

Status: implementado

Ponteiro C que pode ser nulo chega como Optional[*...] e é aberto com match. char* chega cru (não vira string sozinho).

Makoto

v := unsafe c.stdlib.getenv("HOME".to_cstring()) assume "getenv"
match v { some(p) => io.println("definida"); none => io.println("ausente") }

C

char *v = getenv("HOME"); if (v) {...}

Status: implementado

Suas structs são reordenadas; pra cruzar a fronteira use @repr(c) (ordem e padding do C). Structs vindas do @cimport já são @repr(c). Enums C são int-compatíveis (aceitam |/&).

Makoto

@repr(c)
decl Point { x: c.systemv.int; y: c.systemv.int }

C

typedef struct { int x, y; } Point;

Memória do C: @mm(c), @promote, @pin/@unpin

Section titled “Memória do C: @mm(c), @promote, @pin/@unpin”

Status: implementado

Memória que o C te dá fica sob @mm(c) (liberada com o free do C). @promote(gc) repatria pro seu GC. Objeto @mm(gc) passado ao C exige @pin (senão erro de compilação); @unpin é responsabilidade sua.

Makoto

p: *u8 @mm(c) = (unsafe c.stdlib.malloc(16) assume "c").or_panic()
mine := p @promote(gc)
@pin head
// ... C segura o ponteiro ...
@unpin head

C

void *p = malloc(16); /* ... */ free(p);

Status: implementado

Ganha ABI C pra ser passada como ponteiro de função. Corpo não aloca heap nem faz IO (não há processo na thread crua do C); pode enviar em canal e fazer spawn (roteado pelo runtime).

Makoto

@cimport("stdlib.h")
@callback fn bye() {}
_ := unsafe c.stdlib.atexit(bye) assume "registrar handler de saída"

C

void bye(void) {}
atexit(bye);

@extern (exportar pro C / declarar símbolo)

Section titled “@extern (exportar pro C / declarar símbolo)”

Status: implementado

Expõe função com ABI C e nome cru (sem mangling), sem genéricos. Também declara símbolo externo sem corpo. mk build --emit=lib gera .a + header C; mk export cmake|ninja gera a cola de build.

Makoto

@extern fn my_library_init() -> i32 { return 0 }
@extern fn puts(s: [*]u8) -> i32

C

int my_library_init(void); /* no header gerado */

Status: planejado

Primitiva unsafe dedicada pra reinterpretar ponteiro (ex.: comparador do qsort lendo void*). Desenhada, ainda não implementada (reinterpret não é membro de mem).

Makoto

ia := unsafe mem.reinterpret[c.gcc.void, i32](a) assume "a aponta um i32" // planejado

C

const int *ia = (const int *)a;

Dependências C: cdeps no mk.project + mk cc

Section titled “Dependências C: cdeps no mk.project + mk cc”

Status: implementado

O .c entra no build (estilo Zig). Libs C declaradas com versão e checksum (sem auto-descoberta de -l). mk cc é um cc/c++ drop-in com o sysroot musl embutido.

Makoto

mk.project
cdeps {
sqlite3 3.45.0 sha256:...
}
// shell: mk cc -c shim.c -o shim.o

C

gcc main.c -lsqlite3

Fronteira mais baixa, sempre unsafe. Duas formas: função-asm (corpo inteiro em asm, params/retorno ligados a registradores) e bloco inline (instruções no meio do código, com in/out/clobber). Dialeto nativo de cada arquitetura (Intel/AT&T, ARM, RISC-V, WASM); seleção por arquitetura com comptime if.

Função-asm: @asm(x86, att) unsafe fn ...

Section titled “Função-asm: @asm(x86, att) unsafe fn ...”

Status: implementado

Parâmetro e retorno ligados a registrador no próprio parâmetro. Clobbers implícitos (é fronteira de chamada).

Makoto

@asm(x86, att)
unsafe fn mask(a: u64 @asm(in, rax)) -> u64 @asm(out, rax) {
andq $255, %rax
}
r := unsafe mask(x) assume "x86_64"

C

static inline uint64_t mask(uint64_t a) { __asm__("andq $255, %0" : "+r"(a)); return a; }

Status: implementado

Decoradores empilhados acima do unsafe {}; operando pode ser local, campo ou elemento. Clobbers explícitos (correção, não verbosidade). É statement, não expressão.

Makoto

var lo: u32 = 0
var hi: u32 = 0
@asm(x86, intel)
@asm(out, rax) lo
@asm(out, rdx) hi
@asm(clobber, "memory")
unsafe {
rdtsc
}

C

__asm__ volatile("rdtsc" : "=a"(lo), "=d"(hi) :: "memory");

Registrador escolhido pelo compilador: xreg

Section titled “Registrador escolhido pelo compilador: xreg”

Status: implementado

@asm(in, xreg): o alocador escolhe; no corpo use o nome simbólico do operando.

Makoto

@asm(x86, intel)
unsafe fn double(value: u32 @asm(in, xreg)) -> u32 @asm(out, xreg) {
add value, value
}

C

__asm__("add %0, %0" : "+r"(v));

Status: implementado

use arch + comptime if arch.current == .x86_64 { ... }; asm da arquitetura errada não compila (sem fallback mágico). WASM não tem registradores: liga pelos próprios parâmetros.

Makoto

use arch
fn timestamp() -> u64 {
comptime if arch.current == .x86_64 { return unsafe rdtsc_x86() assume "rdtsc disponível" }
return portable_clock()
}
@asm(wasm, wasm)
unsafe fn add(a: u32, b: u32) -> u32 {
local.get a
local.get b
i32.add
}

C

#if defined(__x86_64__) ... #else ... #endif

use é a única palavra de import. Nada é “fundamental demais pra importar”: print vive em io, panic em runtime. Visibilidade em 3 níveis com o meio como padrão. mk.project = binário/composição; mk.mod = módulo (lib) com as próprias deps; mk.sum = checksums. Resolução MVS (estilo Go), sem ciclos.

Status: implementado

use json (acesso qualificado), use strings.{to_upper} (traz nomes), use x as y, e use math.abs(-5.0) inline (chamada avulsa sem poluir o escopo). Prefixo decide a origem: nome puro = stdlib; app/auth = local; github.com/... = externo (precisa estar em deps).

Makoto

use io
use strings.{to_upper}
use collections as col
x := use math.abs(-5.0)

C

#include <stdio.h> /* textual, polui o escopo global */

Status: implementado

priv fn = só o arquivo; sem marca = o módulo inteiro (padrão); pub fn = exportado. Campo de struct inverte o padrão (privado; pub expõe). Capitalização não importa (convenção: snake_case valores, PascalCase tipos).

Makoto

priv fn helper() {}
fn internal() {}
pub fn api() {}

C

static void helper(void) {} /* só o .c */
void api(void); /* no .h */

Status: implementado

Projeto (bin) declara versão da linguagem, deps, cdeps, tasks e strict-mm. Cada mk.mod é um módulo-lib com as próprias deps. Pasta sem mk.mod funde no módulo pai. Import absoluto (nunca ../).

Makoto

mk.project
package app
makoto 0.1.0
deps {
github.com/acme/jwt 1.4.0
}
[tasks.greet]
run = echo hi
subprocess = [echo]
// auth/mk.mod
module auth

C

# Makefile / CMakeLists.txt (sem gerenciador de pacotes)

Status: parcial

Um arquivo transportável com module Nome no topo; o design prevê deps com versão e checksum embutidos no cabeçalho (implementação parcial).

Makoto

module quickscript
deps {
github.com/foo/json 0.9.0 sha256:a1b2c3...
}

C

n/a

Status: implementado

ABA é erro. Inicialização top-level é const (comptime) ou pura; efeito de boot vai num processo explícito.

Makoto

// a.mko: use app/b + b.mko: use app/a -> erro de compilação

C

// ciclos de #include resolvidos com guardas

Métodos em tipo de outro módulo (órfãos)

Section titled “Métodos em tipo de outro módulo (órfãos)”

Status: implementado

Pode definir fn (p: other.Point) norm(). Se dois módulos visíveis definem o mesmo, a chamada sem qualificar é ambígua (erro) — qualifique (A.norm(p)).

Makoto

pub fn (p: geo.Point) norm() -> float { ... }

C

n/a

Pull e desligado por padrão: você só paga pelo que liga. Introspecção (snapshot barato) e trace (stream de eventos num ring buffer). Telemetria de aplicação (métricas, spans, logs) é stdlib.

Introspecção: runtime.processes/process/group/channels/stats/self

Section titled “Introspecção: runtime.processes/process/group/channels/stats/self”

Status: implementado

Handle de processo expõe nome, estado, memória por estratégia de @mm, restarts, supervisor/filhos…

Makoto

use runtime
procs := runtime.processes()
stats := runtime.stats()
n := runtime.self().restarts()

C

/* /proc/self/status, getrusage */

Trace de eventos: runtime.trace(alvo, eventos, ...)

Section titled “Trace de eventos: runtime.trace(alvo, eventos, ...)”

Status: implementado

Eventos .send .receive .spawn .exit .schedule_in .gc .restart .channel_block… Consumo via .buffer (padrão), .channel, .callback(fn), .endpoint; opções sample: N, duration, inherit.

Makoto

t := runtime.trace(.all, .spawn)
spawn worker()
io.println(t.count())

C

/* strace / perf / eBPF, externos */

Status: implementado

Stdlib escrita em Makoto: metrics.counter/gauge/increment/read, tracing.span/set_attribute/finish, log.debug/info/warn/error.

Makoto

use metrics
use tracing
use log
c := metrics.counter("requests")
s := tracing.span("http.request")
log.info("worker started")

C

syslog(LOG_INFO, "worker started");

Testes, benchmarks, property-based e fuzzing

Section titled “Testes, benchmarks, property-based e fuzzing”

mk test roda @test/@bench; @property e @fuzz são da extensão de testes (--ext=test ou no mk.project). Matriz de execução por arquivo com @testmode(ordem, estado).

Status: implementado

Teste solto ao lado do código, ou arquivos x_test.mko / pasta tests/. Cada teste roda como processo.

Makoto

@test fn soma() { assert (1 + 1 == 2) }

C

/* sem framework padrão (Unity, Check, cmocka) */

Status: implementado

Estilo Go: esforço configurável, comparador estatístico fixo (anti p-hacking).

Makoto

@bench fn bench_spin() { spin(50) }

C

/* manual com clock_gettime */

Status: implementado

order: sequential | parallel; state: isolated | shared. Listas geram o produto cartesiano, com relatório por combinação (acha bugs de transição e races em estado lógico/externo).

Makoto

@testmode(sequential, [isolated, shared])
@test fn s_one() { assert (1 == 1) }

C

n/a

@property(runs: N) (property-based com shrinking)

Section titled “@property(runs: N) (property-based com shrinking)”

Status: implementado

Argumentos gerados (gen), falha reduzida ao menor contraexemplo.

Makoto

use test
@property(runs: 50) fn prop_commutes(a: int, b: int) { test.expect(a + b == b + a) }

C

/* theft / rapidcheck (C++) */

@fuzz fn f(data: []u8) (guiado por cobertura)

Section titled “@fuzz fn f(data: []u8) (guiado por cobertura)”

Status: implementado

Corpus e crashes persistidos; alvo recebe bytes arbitrários.

Makoto

use test
@fuzz fn fuzz_utf8(data: []u8) {
match string.from[u8](data) { Ok(s) => test.expect(s.bytes.len == data.len); Err(e) => { let _ := e } }
}

C

int LLVMFuzzerTestOneInput(const uint8_t *d, size_t n) { ...; return 0; } /* libFuzzer */

Primeira extensão oficial do compilador (não é core): arquivo .mkoui liga a gramática de markup. Reatividade fina estilo SolidJS (sem Virtual DOM), ilhas (só o reativo vira WASM/JS no cliente), SSR, CSS com escopo verificado, @server/@client, @jsimport, e transpile pra JS (--target=js).

Status: implementado

Componente = função; props = parâmetros; markup vai no return { ... }. {{ }} = conteúdo entre tags; { } = valor depois de = (prop, atributo dinâmico, handler).

Makoto

@component
fn Greeting(name: string, count: int) {
return {
<div class="card"><h1>Olá, {{name}}</h1><p>{{count}} mensagens</p></div>
}
}
// uso: <Greeting name="Ana" count={5} />

C

n/a

Status: implementado

@state = variável reativa; @derived = recomputada quando dependências mudam; @effect = roda quando dependências mudam (só no cliente); @context = injeção pela árvore (consumidor sem provider = erro de compilação).

Makoto

@client
@component
fn Counter() {
@state n := 0
@derived doubled := n * 2
@effect { io.println("n = {{n}}") }
return { <p>{{doubled}}</p> }
}

C

n/a

Status: implementado

Layout é componente cujos parâmetros slot recebem o conteúdo filho (como children). @component(page) gera documento HTML completo.

Makoto

@component(layout)
fn Shell(body: slot) { return { <main>{{body}}</main> } }
@component
fn Page() { return { <Shell><h1>Oi</h1></Shell> } }

C

n/a

Status: implementado

Restrições, não marcas obrigatórias: @server nunca vai pro cliente (segredos, DB); @client só roda no browser (vira ilha). Sem marca = roda onde for chamado. A restrição propaga (chamar @server de @client = erro).

Makoto

@server
fn load_count() -> int { return 7 }
@component
fn Badge() { return { <span>{{load_count()}}</span> } }

C

n/a

CSS com escopo, @scope(global), @css("arquivo.css")

Section titled “CSS com escopo, @scope(global), @css("arquivo.css")”

Status: implementado

Classes do <style> do componente ganham hash (não colidem); class="X" sem .X definido = erro de compilação (inclusive classes de CSS importado, ex. Tailwind). @scope(global) desliga o hash; class={expr} dinâmico.

Makoto

@component
fn Pill(active: bool) {
return { <span class={ active ? "pill-on" : "pill" }>x</span><style>.pill { color: gray } .pill-on { color: green }</style> }
}

C

n/a

Status: implementado

Análogo ao @cimport: tipos lidos de .d.ts, cola WASMJS gerada. Ambiente declarado propaga (usar dom em código @server = erro de compilação, não window is not defined em runtime).

Makoto

@jsimport("dom", browser)
el := js.dom.getElementById("app")

C

n/a

Status: implementado

O loop do core reaproveitado em posição de conteúdo; forma funcional equivalente com {{ xs.map(...) }}.

Makoto

@component
fn Lista(xs: []int) { return { <ul>loop x in xs { <li>{{x}}</li> }</ul> } }

C

n/a

Desenhada (§21 + verification.md), não implementada ainda (épico MKO-499). Ideia central: toda garantia é uma obrigação sobre um predicado, descarregada no degrau mais alto possível — tipo (0) dobra comptime (1) extensão SMT/model-checker (2) checagem runtime (3) aviso (4) assume humano (5). Nunca cai “pra fora da escada”.

Linear/afim no tipo: @must_consume / @consume_once

Section titled “Linear/afim no tipo: @must_consume / @consume_once”

Status: planejado

Recurso que precisa ser consumido exatamente uma vez (transação) ou no máximo uma (buffer liberado). Typestate via value-params: File[Open] File[Closed].

Makoto

decl Transaction @must_consume { conn: *Connection }
fn close(f: File[Open] @consume) -> File[Closed] { ... }

C

n/a

Status: planejado

Tipo + predicado: alias Balance = i64 @where(i64 >= 0). Subtipagem por implicação; narrowing após if.

Makoto

alias Balance = i64 @where(i64 >= 0)
alias NonEmpty[T] = (xs: List[T]) @where(xs.len > 0)

C

n/a

Status: planejado

@pure: sem efeitos (alocação, spawn, FFI, IO, panic). @total: termina (recursão estrutural / medida decrescente). Obrigatórias em comptime fn usada em posição de tipo.

Makoto

@pure @total
fn fact(n: int) -> int { return n <= 1 ? 1 : n * fact(n - 1) }

C

__attribute__((pure)) /* dica, sem verificação */

Tipos de sessão: @protocol, @sends/@receives, dual

Section titled “Tipos de sessão: @protocol, @sends/@receives, dual”

Status: planejado

Canal tipado por protocolo vira typestate linear que o checker percorre; dual P gera o lado espelhado.

Makoto

@protocol decl Auth {
Credentials @sends
@receives match {
Ok => Token @receives
Deny => Reason @receives
}
}
let c: Channel[Auth] // o servidor recebe Channel[dual Auth]

C

n/a

Tipos dependentes: camada prática e @proof

Section titled “Tipos dependentes: camada prática e @proof”

Status: planejado

Prática: value-params (decl Vec[T, n: usize] { data: [n]T }) — os value-params já existem. Prova (@proof): famílias indexadas/GADTs, normalização simbólica, Σ/Π; prova = programa total (Curry-Howard).

Makoto

decl Vec[T, n: usize] { data: [n]T }
@proof decl Eq[A, x: A, y: A] -> prop { Refl: fn[a: A]() -> Eq[A, a, a] }

C

n/a

Model checking: @spec (extensão) e táticas @by

Section titled “Model checking: @spec (extensão) e táticas @by”

Status: planejado

@spec verifica propriedades temporais de um sistema de processos ancorado num @supervisor (safety/liveness). @by { induction(xs); simp } (táticas) fica pro self-hosting.

Makoto

@spec(spec.always(spec.implies(submitted(req), spec.eventually(committed(req)))))

C

// n/a (CBMC)

Um binário só. Compilador nativo: Go LLVM IR clang embutido; toolchain hermético (musl). Subcomandos atuais: run, build, check, test, analyze, get, export, cc; mk sem argumentos abre o REPL.

Status: implementado

-O0/-O2/--release, --target=<triple> (x86_64, aarch64, riscv64 via qemu, wasm32, js), --emit=lib (estática + header C), flags de GC/scheduler/autofree.

Makoto

// mk run app.mko
// mk build --release --target=aarch64-linux-musl app.mko
// mk check app.mko

C

gcc -O2 -o app main.c

mk test, mk analyze, mk get, mk export, mk cc

Section titled “mk test, mk analyze, mk get, mk export, mk cc”

Status: implementado

Testes; relatório do analisador de memória; baixar deps (Git, MVS); gerar cola CMake/Ninja; cc/c++ drop-in.

Makoto

// mk test
// mk analyze app.mko
// mk get github.com/acme/jwt
// mk export cmake
// mk cc -c shim.c -o shim.o

C

make test; cppcheck; cmake

Formatter, LSP, linter, doc (///), migração, observer, debugger

Section titled “Formatter, LSP, linter, doc (///), migração, observer, debugger”

Status: planejado

Desenhados (§22): compilador como processador de queries incremental (base do LSP), formatter sem config, linter fechado, doc comments /// HTML/Markdown, ferramenta de migração, observer/debugger com breakpoints ligados.

Makoto

/// Soma dois inteiros.
fn add(a: int, b: int) -> int { return a + b }

C

clang-format; clangd; doxygen

Guia prático: portar código C de kernel/syscalls pra Makoto

Section titled “Guia prático: portar código C de kernel/syscalls pra Makoto”

Receita pra reescrever a camada “fala com o kernel” (namespaces, mount, cgroups, processos) — o caso Docker/runc. Padrão central: wrapper seguro = uma fn normal que faz a chamada C dentro de unsafe ... assume "motivo" e devolve Result/error{...}; o resto do programa nunca vê unsafe. Tudo abaixo foi compilado e executado no mk atual; as limitações conhecidas estão marcadas.

Status: parcial

Um @cimport por header; nome do submódulo = basename (sys/mount.h c.mount). Limitação atual: não há como definir _GNU_SOURCE, então no musl somem unshare, setns, clone, CLONE_*. Contorno hoje: um shim C (.c no build, compilado com mk cc) que define _GNU_SOURCE e reexporta o que falta, declarado em Makoto com @extern.

Makoto

@cimport("unistd.h")
@cimport("sys/mount.h")
@cimport("fcntl.h")

C

#define _GNU_SOURCE
#include <unistd.h>
#include <sys/mount.h>
#include <fcntl.h>

Syscall que retorna -1 wrapper com error{...}

Section titled “Syscall que retorna -1 wrapper com error{...}”

Status: implementado

O perigo é abatido dentro do wrapper (assume com o motivo auditável); quem chama só vê um erro tipado.

Makoto

fn set_hostname(name: string) -> error{Failed} {
rc := unsafe c.unistd.sethostname(name.to_cstring(), name.bytes.len as c.systemv.size_t)
assume "buffer válido de name.bytes.len bytes"
if rc != 0 { return error.Failed }
return
}
set_hostname("box") catch |e| { io.println("sethostname falhou") }

C

if (sethostname(name, strlen(name)) != 0) { perror("sethostname"); return -1; }

Status: planejado

O design expõe c.errno.errno como lvalue legível; ainda não implementado. Contorno: shim C int mk_errno(void) { return errno; } declarado com @extern fn mk_errno() -> i32 e chamado logo após a falha.

Makoto

@extern fn mk_errno() -> i32 // implementado num shim .c
rc := unsafe c.unistd.chdir("/x".to_cstring()) assume "path NUL-terminado"
if rc != 0 { e := unsafe mk_errno() assume "logo após a chamada que falhou" }

C

if (chdir("/x") != 0) { int e = errno; if (e == ENOENT) {...} }

Status: parcial

Enums C e #define numérico literal funcionam com | e &. Constantes chegam como int da Makoto: converta para o tipo do parâmetro (as c.systemv.int / as u64). Limitação: macro com expressão (MS_PRIVATE (1<<18), SYS_pivot_root) é descartada — defina a constante em Makoto (const MS_PRIVATE: u64 = 262144).

Makoto

fd := unsafe c.fcntl.open(path.to_cstring(), c.fcntl.O_RDONLY as c.systemv.int) assume "path NUL-terminado"
const MS_PRIVATE: u64 = 262144 // (1<<18), até macros-expressão serem suportadas
rc := unsafe c.mount.mount(none, "/".to_cstring(), none, c.mount.MS_REC as u64 | MS_PRIVATE, none)
assume "argumentos válidos de mount(2)"

C

int fd = open(path, O_RDONLY);
mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL);

Buffers, goto cleanup e close(fd) @mm(none) + defer

Section titled “Buffers, goto cleanup e close(fd) @mm(none) + defer”

Status: implementado

Buffer cru via mm.alloc (o gate local exige o free); limpeza com defer (o defer aceita uma chamada — use um helper pra close).

Makoto

fn close_fd(fd: c.systemv.int) { _ := unsafe c.unistd.close(fd) assume "fd aberto por open" }
@mm(none)
fn read_file(path: string) -> int {
fd := unsafe c.fcntl.open(path.to_cstring(), c.fcntl.O_RDONLY as c.systemv.int) assume "path NUL-terminado"
if fd < 0 { return -1 }
defer close_fd(fd)
let buf: [*]u8 = mm.alloc(4096)
defer free(buf)
n := unsafe c.unistd.read(fd, buf, 4096) assume "buf tem 4096 bytes"
return n as int
}

C

int read_file(const char *path) {
int fd = open(path, O_RDONLY);
if (fd < 0) return -1;
char *buf = malloc(4096);
ssize_t n = read(fd, buf, 4096);
free(buf); close(fd);
return (int)n;
}

Status: parcial

Entrada: s.to_cstring(). Saída: char* chega cru (*c.<mod>.char, NULL Optional); from_cstring pra converter em string ainda é planejado.

Makoto

v := unsafe c.stdlib.getenv("PATH".to_cstring()) assume "getenv"
match v { some(p) => io.println("tem PATH"); none => io.println("sem PATH") }

C

const char *v = getenv("PATH");
if (v) puts(v);

Structs do kernel (struct stat, sockaddr…)

Section titled “Structs do kernel (struct stat, sockaddr…)”

Status: implementado

Vêm do @cimport com layout C automático. As suas que vão pro C: @repr(c). Struct com bitfield chega opaca (segure o ponteiro, não leia campos). Limitação atual: se existe função com o mesmo nome da tag (struct stat × stat()), c.stat.stat resolve pra função — a struct fica inacessível (registrado).

Makoto

@repr(c)
decl Req { op: u32; len: u32 }

C

struct req { uint32_t op, len; };

Ponteiro de função (clone(fn, ...), qsort, atexit)

Section titled “Ponteiro de função (clone(fn, ...), qsort, atexit)”

Status: implementado

@callback fn tem ABI C. Restrição: corpo sem alocação no heap e sem IO (roda numa thread crua do C) — pode enviar em canal / fazer spawn roteado pelo runtime.

Makoto

@callback fn bye() {}
_ := unsafe c.stdlib.atexit(bye) assume "registrar handler de saída"

C

static void bye(void) {}
atexit(bye);

Atenção: fork/unshare/setns × runtime multi-thread

Section titled “Atenção: fork/unshare/setns × runtime multi-thread”

Status: parcial

O runtime da Makoto tem threads de worker (scheduler work-stealing). Operações que exigem processo single-thread — unshare(CLONE_NEWUSER), setns num user namespace, fork() sem exec imediato — batem no mesmo problema do Go, que o runc resolve com o nsexec em C rodando antes do runtime subir. Rota análoga em Makoto: shim C com __attribute__((constructor)), ou fork+exec de um helper. Um spawn da Makoto é processo leve do runtime, não processo do SO: namespace/cgroup é sempre FFI.

Makoto

// processo Makoto (leve, isolado pelo runtime):
spawn worker(cfg) catch |e| { ... }
// processo do SO (namespaces, cgroups): via FFI (clone/fork/exec) — de preferência num shim C

C

/* runc/libcontainer/nsenter/nsexec.c: __attribute__((constructor)) void nsexec(void) { ... unshare/setns ... } */

Supervisionar o que o C faria com waitpid em loop

Section titled “Supervisionar o que o C faria com waitpid em loop”

Status: implementado

Um processo Makoto por container/tarefa, supervisionado com @supervisor e registrado por nome; a morte chega como erro estruturado no catch |e| (não precisa de loop de waitpid + contador de restarts).

Makoto

@supervisor(max_restarts: 5, window: 1m)
@register("container_abc")
spawn run_container(cfg) catch |e| { log.warn("container caiu"); runtime.supervisor.restart() }

C

for (;;) { pid_t p = fork(); if (p == 0) run(); waitpid(p, &st, 0); if (++restarts > 5) break; }

Status: implementado

O .c do shim entra no build com o toolchain hermético; libs C externas vão em cdeps com versão e checksum. mk export cmake se o resto do projeto for CMake.

Makoto

// mk cc -c nsshim.c -o nsshim.o
// mk.project:
cdeps {
libseccomp 2.5.5 sha256:...
}

C

cc -D_GNU_SOURCE -c nsshim.c && cc main.o nsshim.o -lseccomp