Sistema de módulos
O que é um módulo: a unidade
Seção intitulada “O que é um módulo: a unidade”A organização tem dois marcadores com papéis distintos. É o split lib/bin (Rust, Zig) e go.mod/go.work, e separá-los é o que mantém a detecção de raiz trivial:
mk.mod= um módulo, e um módulo é uma lib: uma caixa independente e reutilizável, com suas próprias dependências (lista as deps externas que aquele módulo importa). É fronteira de namespace e visibilidade e de dependência. Pastas semmk.modse fundem no namespace do módulo pai (estilo Go). Um módulo nunca é compilado sozinho, só como parte da árvore de um projeto; daí módulos não-usados convivem sem custo, e N projetos compartilham N módulos (módulos são libs).mk.project= o bin ou compositor, o módulo de topo e a unidade de compilação. Compõe os módulos que usa numa árvore compilada, e declara a versão da linguagem, suas deps externas e as tasks, a precedência e a produção de binário (seção 22). É a raiz da detecção de path. Um projeto produz um artefato; um módulo, não.
A unidade física mapeia 1:1 com a lógica, e um arquivo é no máximo um módulo (não há vários module por arquivo), então “onde mora o módulo X?” é sempre respondível pelo path, sem abrir nada. Recursivamente, as subpastas que não têm seu próprio mk.mod (ou mk.project) pertencem ao módulo pai, e os arquivos de um módulo compartilham o mesmo namespace, sem use entre eles.
Detecção de raiz: subir do arquivo atual até o primeiro mk.project. Como há exatamente um por projeto, é inequívoco, no papel do go.mod.
Módulo single-file (module Nome no topo de um arquivo): um módulo de transporte fácil, um arquivo só, que declara seus próprios imports e libs externas, com checksums e versões embutidos (ver “Manifesto”, abaixo). Rodado sozinho, é o programa inteiro; colocado dentro de um projeto, vira um submódulo sem submódulos próprios.
Árvore e resolução de path
Seção intitulada “Árvore e resolução de path”myapp/ ← mk.project (BIN: compõe módulos, tasks, deps próprias)├── mk.project├── mk.sum (checksums das deps resolvidas, auto-gerado; serve projeto E módulos)├── main.mko → módulo "myapp" (o topo também é módulo/lib)├── auth/ ← mk.mod (LIB "myapp/auth", deps PRÓPRIAS no seu mk.mod)│ ├── tokens.mko → módulo "myapp/auth"│ └── internal/ (sem mk.mod → parte de "myapp/auth", mesmo namespace)│ └── hash.mko → módulo "myapp/auth"├── store/ ← mk.mod (LIB "myapp/store", deps próprias)│ └── db.mko├── experimental/ ← mk.mod (LIB não-usada pelo bin → convive, NÃO é compilada)│ └── draft.mko└── tools/ └── migrate/ ← mk.project (SUBPROJETO/bin, árvore e deps PRÓPRIAS) └── main.mko → a árvore reinicia AQUIImports são absolutos a partir do nome do pacote, nunca via ../:
main.mkofazuse myapp/auth,use myapp/store(vê opubdeles).auth/tokens.mkofazuse myapp/store(absoluto pro irmão; nunca../store); enxergainternal/hashsemuse(mesmo módulo).tools/migrate/main.mko: subir achatools/migrate/mk.projectprimeiro, logo a raiz émigrate, que não enxergamyapp/...(se precisar, depende explicitamente).
A regra de nested é, então, dois casos diferentes:
mk.modnested = uma lib mais funda no mesmo projeto. É fronteira de visibilidade e de dependência (tem suas deps), não um bin isolado: pode importar pai ou irmão pelo path absoluto (sujeito apub), e é compilada como parte da árvore do projeto que a usa. “Sem acesso ao pai” significa sem acesso implícito e sem../, não isolamento total.mk.projectnested = um subprojeto ou bin de verdade (árvore e deps próprias, produz seu próprio artefato). Esse é o caso em que a árvore genuinamente reinicia e o pai não alcança: vendoring, sub-ferramentas (o equivalente a umgo.work/go.modaninhado).
Isso elimina a ambiguidade de imports relativos (o problema do Python), pois todo path diz de onde vem, e mantém a detecção de raiz trivial (um só mk.project por projeto). A keyword de import é use (qualificado pelo módulo, seletivo com .{}, e … as pra alias); está na seção 14.
Visibilidade: três níveis
Seção intitulada “Visibilidade: três níveis”Três níveis que batem com a estrutura física, e o default é o do meio, privado-ao-módulo, o caso comum de arquivos do mesmo módulo colaborando, sem marca (Princípio 1):
priv fn f() // privado ao ARQUIVO: nem os outros arquivos do módulo veemfn f() // privado ao MÓDULO (default): arquivos do módulo veem, fora nãopub fn f() // público: outros módulos veemO não-marcado é privado-ao-módulo porque colaboração intra-módulo é comum (você divide um módulo em arquivos que cooperam); pub é a exposição consciente pra fora; priv é a marca rara pra esconder até dos vizinhos.
Campos de struct invertem o default, e não é inconsistência, é o mesmo Princípio 1 sobre algo com “caso seguro” diferente. Código quer colaborar (default visível-no-módulo); dado quer se proteger (default fechado). Campo exposto é controle abdicado (qualquer um lê e escreve, e o invariante não se mantém), então o seguro é fechado, e pub expõe individualmente:
decl User { id: int // privado (default): encapsulado name: string // privado pub email: string // exposto individualmente a outros módulos}Manifesto, pacotes e dependências
Seção intitulada “Manifesto, pacotes e dependências”Módulo é uma lib (unidade de código reutilizável, com deps próprias); projeto é um bin (compõe módulos numa árvore e produz o artefato); pacote é a unidade de distribuição, um módulo (lib) publicado com nome e versão. Deps moram nos dois manifestos: cada mk.mod lista as deps daquele módulo, e o mk.project lista as deps do bin mais a composição, as tasks e a precedência (seção 22). O build resolve a união por MVS (seleção de versão mínima, estilo Go: reproduzível, sem solver) e trava em mk.sum.
// mk.mod: a lib "auth" declara o que ELA importamodule authdeps { github.com/acme/jwt 1.4.0}// mk.project, o bin: deps próprias + composição/tasks (detalhadas na seção 22)package myappmakoto 1.2deps { github.com/acme/http 2.3.1 github.com/foo/json 0.9.0}Dependências externas entram via Git, como em Go (mk get <repo>), com versionamento e pinagem; a resolução é MVS sobre a união das deps de todos os módulos da árvore compilada.
De onde vem cada use é decidido pelo prefixo, sem ambiguidade entre stdlib, externo e local:
use json: a forma bare é stdlib (vem com o compilador; sem versão nem checksum, não atravessa a rede).use myapp/auth: o prefixo nome-do-pacote é local (mesmo projeto).use github.com/acme/http: a forma URL é externo (tem que estar nodeps).
Versão vs. checksum: coisas diferentes. A versão é qual código você quer (2.3.1 é um ponteiro pra uma tag Git, e tags podem ser reescritas, repos recriados, downloads interceptados). O checksum é a prova de que é exatamente aquele código (sha256:… só bate com aqueles bytes). Versão é intenção, editada à mão; checksum é trava verificável, gerada pela máquina, para build reproduzível e contra supply-chain. É o par go.mod/go.sum, Cargo.lock. O checksum existe só para deps externas (a fronteira por onde código não-confiável entra); stdlib e local não têm.
Onde o checksum mora difere por modo, mas ele sempre existe:
- Projeto e módulo: versão à mão (no
mk.projectdo bin, nomk.modde cada lib), checksum auto-gerado nomk.sum, que serve aos dois (locka as deps resolvidas da árvore). Arquivos separados porque projeto e módulo já são pastas. - Single-file: versão e checksum embutidos no cabeçalho, na mesma linha, porque o arquivo é feito pra viajar (chat, gist, pendrive), e é justamente aí que a trava mais importa (o “0.9.0” da registry pode não ser o que rodou na origem):
module quickscriptdeps { github.com/foo/json 0.9.0 sha256:a1b2c3… // versão + trava juntas, embutidas}O building-block do workspace já existe: como módulos são libs compartilháveis, N projetos num monorepo referenciam N módulos comuns sem cerimônia. Só a orquestração explícita multi-bin (um go.work que lista vários projetos-irmãos como alvo único) fica deferida: o Go só a adicionou anos depois, e projeto-único mais subprojeto-mk.project mais módulos-libs-compartilhados já cobrem o caso comum.
Interação com comptime
Seção intitulada “Interação com comptime”use é resolvido em compilação, e um módulo pode ser valor de comptime, habilitando use condicional para seleção em compile-time (escolher um backend por arquitetura, por exemplo):
const backend := use(if target.arch == x86 { return "backend_x86" } else { return "backend_wasm" })E como módulo é código-fonte importado (não um binário com tipos congelados), o comptime de um módulo importado roda na sua compilação, junto com o seu: List[int] importado de collections é monomorfizado no seu build, com o seu int. Não existe “genérico que não cruza módulo”: tudo é fonte recompilada junto, no ponto de uso (modelo Zig).
Ciclos e inicialização
Seção intitulada “Ciclos e inicialização”Imports circulares são proibidos (ABA é erro de compilação), pois ciclo de módulos é dor garantida de inicialização e de raciocínio.
Sem ciclos, o grafo de dependências é sempre ordenável, e a ordem de inicialização é a topológica dele: se A importa B, B inicializa antes (A depende de B). Entre irmãos independentes (nenhum importa o outro) a ordem é indefinida, mas irrelevante, porque inicialização de topo é const (comptime, sem efeito) ou pura, e nada observa a diferença. Efeito colateral de startup (abrir arquivo, registrar handler) não mora num var global escondido, e sim num processo de boot explícito (seção 8), com ordem declarada. Isso dá inicialização previsível sem um sistema de ordenação de init.