Pular para o conteúdo

Comparações

Makoto × Rust

makoto-vs-rust.md · 4.031 linhas · 62 min de leitura

Mapeamento conceito a conceito da linguagem Makoto para Rust. 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. 173 conceitos — implementado: 146 · parcial: 7 · planejado: 10 · não existe (por design): 10

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

Outras comparações: C · JavaScript · TypeScript · Java · 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
}

Rust

fn find(xs: &[i32], t: i32) -> Option<usize> {
xs.iter().position(|&x| x == t)
}

Diferença: Equivalente direto: Option<T> Optional[T], Some some, None none.

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

Rust

fn parse(s: &str) -> Result<i32, ParseErr> {
if s.is_empty() { return Err(ParseErr::Empty); }
Ok(42)
}
let v = parse(s)?;

Diferença: Muito próximo de Result, mas Makoto não tem ?: a propagação é escrita (catch |e| { return e }).

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())

Rust

struct Point { x: i32, y: i32 }
impl Point { fn sum(&self) -> i32 { self.x + self.y } }

Diferença: Parecido com impl, mas o método é declarado solto, com o receiver nomeado (p), sem bloco impl.

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
}

Rust

async fn handle(p: &str) -> std::io::Result<String> {
tokio::fs::read_to_string(p).await
}

Diferença: Sem async/.await/runtime externo (tokio): o runtime da Makoto já é o scheduler.

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

Rust

trait ToS { fn to_s(&self) -> String; } // impl por tipo

Diferença: Trait por tipo é o mais próximo; sem impl Add pra tipo do usuário.

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" }

Rust

fn dbl(x: i32) -> i32 { x * 2 } // última expressão = retorno

Diferença: O estilo ‘última expressão’ do Rust é proibido em Makoto.

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()

Rust

let total: i32; // precisa atribuir antes de ler

Diferença: Rust permite declarar e atribuir depois; Makoto não.

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

Rust

let x = 5;
let y: i32 = 10;

Diferença: Igual ao let do Rust, mas sem shadowing no mesmo escopo.

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

Rust

let mut n = 0; n += 3;

Diferença: var let mut. Rust só avisa (warning) de mut desnecessário; Makoto dá erro.

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

Rust

const SIZE: usize = 64;
const F: u64 = fact(5); // const fn

Diferença: const fn do Rust comptime fn.

Status: implementado

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

Makoto

_ := compute()

Rust

let _ = compute();

Diferença: Igual; Rust só avisa com #[must_use], Makoto exige sempre.

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

Rust

let (q, r) = divmod(7, 2);
std::mem::swap(&mut a, &mut b);

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

Rust

let b: u8 = 255;
let big: u128 = 1;

Diferença: Rust tem larguras fixas; u3 precisa de crate (ex.: arbitrary-int).

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

Rust

let y = x.wrapping_add(10);
let z = x + 10; // panic em debug, embrulha em release

Diferença: Makoto = checked sempre (debug e release); +% wrapping_add.

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

Rust

let z = 300u32 as u8; let i = 3.9f64 as i32;

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)

Rust

let z = 0.0_f64 / 0.0; // NaN; f64 é só PartialOrd

Diferença: Makoto: sem NaN float é ordem total (como Ord).

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

Seção intitulada “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)

Rust

// crates: num-bigint, rust_decimal, rug (MPFR)

Diferença: Sem bignum na std do Rust.

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 = 'é'

Rust

let k: char = 'w'; // scalar Unicode (≈ codepoint)

Diferença: char do Rust codepoint da Makoto.

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 }

Rust

let t = Duration::from_millis(300);

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)

Rust

let n: usize = 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)

Rust

type Uuid = u8;

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

Rust

let o = 5.cmp(&3); // Ordering::Greater

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) }

Rust

struct Marker;
enum Never {}
fn die(m: &str) -> ! { panic!("{m}") }

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") }

Rust

if n > 0 {...} else if n < 0 {...} else {...}

Diferença: Em Rust if é expressão; em Makoto não (use o ternário).

Status: implementado

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

Makoto

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

Rust

let icon = if liked { "♥" } else { "♡" };

Diferença: Rust não tem ?:.

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

Seção intitulada “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) }

Rust

loop { break }
while n > 0 { n -= 1 }
for x in &xs { println!("{x}") }
for i in 1..=3 {}

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) }

Rust

for i in 0..3 { println!("{i}") }

Diferença: Rust não tem for estilo C.

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 } }
}

Rust

'outer: for i in 0..3 { for j in 0..3 { continue 'outer; } }

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 }

Rust

let x = loop { break 42; };

Diferença: Rust usa break valor; Makoto usa return (e break não carrega valor).

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()
}

Rust

match code { 200 | 201 | 204 => ok(), 404 | 410 => gone(), _ => 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 }
}

Rust

let v = match 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
}

Rust

let f = File::open("cfg")?; // Drop fecha no fim do escopo

Diferença: Rust usa RAII/Drop; Makoto usa defer explícito.

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") }

Rust

panic!("estado inválido");

Diferença: Panic do Rust derruba a thread; o da Makoto derruba o processo leve e é supervisionável.

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}}") }

Rust

fn add(a: i32, b: i32) -> i32 { 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

Rust

// sem default: Option<T> ou builder

Diferença: Rust não tem parâmetro default.

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()) } }

Rust

fn sum(xs: &[i32]) -> i32 { xs.iter().sum() } // sem variádico (macro)

Diferença: Rust só tem variádico em macros.

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)

Rust

let double = |n: i32| n * 2;

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 })

Rust

let ys: Vec<_> = xs.iter().map(|n| n * 10).collect();

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)
}

Rust

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

Diferença: Idêntico ao Rust: fn interna não captura.

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

Rust

impl Counter { fn get(&self) -> i32 { self.n } fn inc(&mut self) { self.n += 1 } }

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()

Rust

impl Stack { fn new() -> Self { Stack { n: 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)

Rust

trait ToS { fn to_s(&self) -> String; } impl ToS for bool {...} impl ToS for i32 {...}

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"
}
}

Rust

// trait com impl por tipo, ou TypeId em runtime

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}

Rust

struct Vec2 { x: f64, y: f64 }
let b = Vec2 { x: 1.0, y: 2.0 };

Diferença: Rust também reordena campos por padrão (#[repr(C)] @repr(c)).

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

Seção intitulada “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
}

Rust

pub struct User { id: i64, pub email: String }

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)
}

Rust

enum Shape { Circle(f64), Rect(f64, f64) }
match s { Shape::Circle(r) => ..., Shape::Rect(w, h) => ... }

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

Rust

// sem equivalente direto (variantes não são tipos); use struct por variante

Diferença: Rust não tem refinamento de enum por variante.

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

Rust

paint(Color::Green);

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() }

Rust

trait Shower { fn show(&self) -> String; }
impl Shower for P { fn show(&self) -> String {...} }

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

Seção intitulada “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)

Rust

let d: &dyn Shower = &p; // vtable
fn greet<T: Shower>(v: T) // monomorfizado

Diferença: Igual a dyn Trait × T: Trait.

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)

Rust

fn log(xs: &[&dyn Display]) {...}

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)

Rust

impl fmt::Display for P { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "P({})", self.x) } }

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

Rust

struct B { a: A, w: i32 } // sem promoção (Deref é o hack)

Diferença: Go-like embedding, mas a promoção é anunciada no decorator.

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] }

Rust

struct Node { val: i32, next: Option<Box<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

Rust

let t = (1, "x"); let (x, y) = t;

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"

Rust

union FloatBits { f: f32, bits: u32 }
let y = unsafe { FloatBits { f: 1.5 }.bits }; // ou f32::to_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 }

Rust

#[repr(C)] struct CPoint { x: i32, y: i32 }
// bitfields: crate bitfield/modular-bitfield

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

Rust

bitflags! { struct Perms: u8 { const READ = 1; const WRITE = 2; } }

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)

Rust

let xs: Vec<i32> = vec![1, 2, 3];
let arr: [i32; 3] = [1, 2, 3];

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]

Rust

let mut ys = xs.clone(); // `let ys = xs` MOVE

Diferença: Rust move; Makoto copia (semântica), sem invalidar xs.

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

Rust

let v = &mut xs[..]; // view
let c = xs[0..2].to_vec(); // cópia

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))

Rust

let mut xs = Vec::new(); xs.push(1); xs[0]; xs.get(9).copied().unwrap_or(-1);

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)

Rust

let mut m = HashMap::new(); m.insert("a", 1); *m.get("a").unwrap_or(&0);

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

Seção intitulada “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)

Rust

xs.iter().map(|n| n * 10).collect::<Vec<_>>();
xs.iter().fold(0, |a, n| a + n);

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()

Rust

let v = find(&xs, 5).unwrap_or(-1);
let w = find(&xs, 5).unwrap(); // ↔ or_panic

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

Rust

let s: String = "héllo".into(); // UTF-8 garantido

Diferença: Modelo do Rust: String UTF-8 + Vec pra bytes.

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

Seção intitulada “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

Rust

s.chars().nth(1) // grafema: crate unicode-segmentation
s.as_bytes()[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")

Rust

println!("Olá {name}, {} itens", 2 + 3);

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

Seção intitulada “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

Rust

a == b // normalização/colação: crates unicode-normalization, icu

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 + "!"

Rust

let mut s = String::from("ab"); s.push_str("c");

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

Seção intitulada “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 }

Rust

s.encode_utf16().collect::<Vec<u16>>();
String::from_utf16(&v)?;

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") }

Rust

CString::new("HOME")?; CStr::from_ptr(p).to_str()?

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")

Rust

"a,b,c".split(',').collect::<Vec<_>>().join("-"); "abc".to_uppercase();

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)
}

Rust

fn risky(n: i32) -> Result<i32, RiskErr> {...}

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")
}

Rust

match load(p) { Ok(v) => ..., Err(E::NotFound) => ..., Err(E::Denied) => ... }

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

Rust

let v = risky(x)?;
let w = risky(x).expect("falhou");

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
}

Rust

let v = match risky(n) { Ok(v) => v, Err(E::Neg) => { ...; return Err(E::Neg) }, Err(e) => return Err(e) };

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)
}

Rust

enum LoadErr { Open(OpenErr), Parse(ParseErr) } // + impl From

Diferença: Em Rust você escreve o enum e os From; em Makoto a união é estrutural.

Sucesso sem valor: retornar só o conjunto de erro

Seção intitulada “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") }

Rust

fn send_it(ok: bool) -> Result<(), Full> {...}

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") }

Rust

fn ext() -> Result<i32, Box<dyn Error>>

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) }

Rust

fn never_fails() -> Result<i32, Infallible> { Ok(1) }

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

Rust

enum E { Http500, PortError(i32) }

Memória: gerenciamento sem cor, ponteiros, ownership

Seção intitulada “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

Rust

let xs = vec![1, 2, 3]; // ownership + Drop, sem GC

Diferença: Rust não tem GC; Makoto tem GC por padrão e manual opt-in.

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)
}

Rust

let a = unsafe { std::alloc::alloc(layout) as *mut i32 };
unsafe { std::alloc::dealloc(a as *mut u8, layout) };

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

Seção intitulada “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

Rust

// o borrow checker pega isso (e muito mais, com anotações de lifetime)

Diferença: Makoto prova só o fragmento decidível, sem lifetimes; o resto vira aviso (analyzer) ou é sua responsabilidade.

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
}

Rust

let bump = bumpalo::Bump::new(); let x = bump.alloc(5); // crate bumpalo

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() { ... }

Rust

// allocator por coleção: Vec::new_in(alloc) (nightly allocator_api)

Diferença: Makoto NÃO passa allocator por parâmetro: isso colore a função.

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

Seção intitulada “@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) }

Rust

fn free_it(p: Box<[i32]>) { drop(p) } // move por valor

Diferença: Equivale a receber por valor (move) em Rust.

@transfer: mover entre processos (invalida a origem)

Seção intitulada “@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

Rust

tx.send(v).unwrap(); // move; v não pode mais ser usado

Diferença: Igual ao move do send do Rust, mas checado só no ponto de transferência.

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

Seção intitulada “@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

Rust

let mine = src.clone();

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") }

Rust

// sem equivalente por tarefa

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] }

Rust

let p: &mut i32 = &mut 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"

Rust

let b = unsafe { a.add(1) };
let x = unsafe { *b };

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

Rust

fn bump(v: &mut i32) { *v += 1 }
bump(&mut x);

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

Rust

// Drop determinístico no fim do escopo

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

Seção intitulada “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)

Rust

// o borrow checker é obrigatório

Diferença: Rust: obrigatório em todo programa. Makoto: opt-in, e só o provado bloqueia.

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

Seção intitulada “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

Rust

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)

Rust

std::thread::spawn(move || worker(data)); // ou tokio::spawn

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)

Rust

let (tx, rx) = std::sync::mpsc::sync_channel::<i32>(0); // 64 = buffer

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}}"
}

Rust

fn worker(rx: Receiver<i32>, tx: Sender<String>)

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)
}

Rust

tx.send(42)?;
let v = rx.recv()?; // Err quando todos os Sender caem

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

Seção intitulada “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 }

Rust

rx.recv_timeout(Duration::from_secs(1))?;

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")
}

Rust

crossbeam::select! { recv(a) -> v => ..., recv(b) -> v => ..., default(Duration::from_millis(100)) => ... }

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") }

Rust

// sem registro nativo (ractor/actix têm)

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

Rust

// tokio multi-thread / current_thread

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 }
}

Rust

let n = Arc::new(Mutex::new(0)); *n.lock().unwrap() += d;

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") }

Rust

let h = thread::spawn(f); if h.join().is_err() { /* panicou */ }

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")
}

Rust

match h.join() { Ok(_) => normal, Err(payload) => crashed }

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()
}

Rust

// sem std (crates ractor/bastion)

Estratégias: one_for_one / one_for_all / rest_for_one

Seção intitulada “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| { ... }

Rust

// manual / crates

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() }

Rust

// 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)
}

Rust

let l = TcpListener::bind("0.0.0.0:8080")?; for s in l.incoming() { /* serde */ }

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 }

Rust

let mut s = TcpStream::connect("127.0.0.1:8080")?;

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

Rust

fn id<T>(x: T) -> T { x }

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}

Rust

struct Box<T> { v: T }
let b = Box { v: 5 };

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() }

Rust

fn show<T: Display>(x: T) -> String { x.to_string() }

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() }

Rust

trait Small: sealed::Sealed {} impl Small for i8 {} impl Small for u16 {}

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

Rust

fn zeros<const N: usize>() -> usize { N * 2 }
zeros::<4>();

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) }

Rust

// sem HKT (GATs cobrem parte)

Diferença: Rust não tem HKT; Makoto tem uma versão limitada de propósito.

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

Rust

const fn repeat(...) // const fn é mais restrita

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

Seção intitulada “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") }

Rust

#[cfg(target_arch = "x86_64")]
println!("x86");

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
}

Rust

// proc-macro (derive) em compile-time

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)

Rust

#[derive(Serialize, Deserialize, Hash)] struct User {...} // serde

@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) }

Rust

// gen blocks ainda instáveis; impl Iterator manual ou (0..).take(5)

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()

Rust

(0..).filter(|n| n % 2 == 0).map(|n| n * 10).take(3)

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] }

Rust

debug_assert!(i < buf.len()); // contratos: crate `contracts`

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) }

Rust

// crate contracts: #[ensures(ret > 0)]

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
}

Rust

assert!(x > 0);

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"

Rust

unsafe fn raw_at(xs: &[i32], i: usize) -> i32 { *xs.get_unchecked(i) }
let v = unsafe { raw_at(&xs, 1) };

Diferença: Rust: unsafe {} é região de permissão silenciosa. Makoto: cada operação declara como o perigo foi neutralizado.

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

Seção intitulada “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

Rust

const _: () = assert!(size_of::<f32>() == size_of::<u32>());

@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"

Rust

// bindgen gera bindings; chamadas em unsafe {}

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

Rust

let n: std::ffi::c_int = 5;

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") }

Rust

let p = unsafe { libc::getenv(k.as_ptr()) }; if !p.is_null() {...}

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 }

Rust

#[repr(C)] struct Point { x: c_int, y: c_int }

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

Rust

let p = unsafe { libc::malloc(16) }; unsafe { libc::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"

Rust

extern "C" fn bye() {}
unsafe { libc::atexit(bye) };

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

Rust

#[no_mangle] pub extern "C" fn my_library_init() -> i32 { 0 }

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

Rust

let ia = a as *const i32;

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

Rust

# Cargo.toml + build.rs (cc crate)

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.

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"

Rust

unsafe { asm!("and {0}, 255", inout(reg) 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
}

Rust

unsafe { asm!("rdtsc", out("eax") lo, out("edx") hi); }

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
}

Rust

asm!("add {0:e}, {0:e}", inout(reg) 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
}

Rust

#[cfg(target_arch = "x86_64")] fn timestamp() -> u64 {...}

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)

Rust

use std::io;
use crate::strings::to_upper;
use std::collections as col;

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() {}

Rust

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

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

Rust

# Cargo.toml + Cargo.lock

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...
}

Rust

// cargo script (-Zscript)

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

Rust

// ciclos entre módulos de um crate são permitidos; entre crates não

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 { ... }

Rust

// regra órfã proíbe impl de trait externo em tipo externo

Diferença: Makoto permite e resolve por visibilidade.

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

Seção intitulada “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()

Rust

// sem equivalente na std (tokio-console)

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

Seção intitulada “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())

Rust

// tracing crate (instrumentação manual)

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")

Rust

log::info!("worker started"); // crates log/tracing/metrics

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) }

Rust

#[test] fn soma() { assert_eq!(1 + 1, 2); }

Status: implementado

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

Makoto

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

Rust

#[bench] fn spin(b: &mut Bencher) {...} // nightly; criterion na prática

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) }

Rust

// cargo test -- --test-threads=1

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) }

Rust

proptest! { #[test] fn commutes(a: i32, b: i32) { prop_assert_eq!(a.wrapping_add(b), b.wrapping_add(a)); } }

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 } }
}

Rust

fuzz_target!(|data: &[u8]| { ... }); // cargo-fuzz

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} />

Rust

#[component] fn Greeting(name: String, count: i32) -> impl IntoView { view! { <div>...</div> } } // Leptos

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> }
}

Rust

let (n, set_n) = signal(0); let doubled = move || n.get() * 2; // Leptos

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> } }

Rust

#[component] fn Shell(children: Children) -> impl IntoView { view! { <main>{children()}</main> } }

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> } }

Rust

#[server] async fn load_count() -> Result<i32, ServerFnError> { Ok(7) } // Leptos

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

Seção intitulada “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> }
}

Rust

// stylers / leptos-style

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")

Rust

use web_sys::window; // wasm-bindgen

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> } }

Rust

view! { <ul>{xs.into_iter().map(|x| view! { <li>{x}</li> }).collect_view()}</ul> }

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

Seção intitulada “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] { ... }

Rust

// ownership afim nativo; #[must_use] só avisa

Diferença: Rust é afim por padrão; linear de verdade não existe.

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)

Rust

// crates: refinement, nutype (checagem em runtime)

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) }

Rust

// const fn aproxima pureza; sem checagem de terminação

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

Seção intitulada “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]

Rust

// crates: session_types, ferrite

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] }

Rust

struct Vec<T, const N: usize> { data: [T; N] } // const generics = camada prática

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)))))

Rust

// kani (model checker)

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

Rust

cargo run --release

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

Rust

cargo test; cargo add; cargo clippy

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

Seção intitulada “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 }

Rust

rustfmt; rust-analyzer; clippy; rustdoc