Comptime e reflexão
A redefinição: metaprogramação é biblioteca, não mágica. Comptime é código puro rodando no compilador; reflexão não tem builtin nenhum.
O conceito
Seção intitulada “O conceito”Gerar código a partir de tipos (serializar, hashear, derivar) precisa de alguma forma de
metaprogramação. As escolhas históricas: macros (C, substituição de texto), macros procedurais
(Rust, uma segunda linguagem), ou um punhado de builtins (@sizeOf, @typeName, em Zig). Cada uma
adiciona um modo de pensar separado da linguagem.
Como Makoto redefine
Seção intitulada “Como Makoto redefine”Comptime (estilo Zig) é simplesmente “avalie isto em compilação”: aplica-se a binding, bloco,
loop, match e fn. type é um valor de primeira classe: um generic é uma função que recebe e
devolve type, executada pelo compilador. Não há linguagem separada: é o mesmo código, rodando
mais cedo.
Reflexão tem zero builtins. Não existe @type_name/@size_of no escopo. Tudo mora no pacote
compiler: reflect(T) (a estrutura do tipo, casada com comptime match reflect(T).kind), fail(msg)
(aborta a compilação com mensagem). É assim que serialize/hash/json se auto-derivam, sem você
escrever nada, e mesmo assim nada disso é builtin.
E o comptime match reflect(T).kind é exaustivo: o compilador exige que você trate todos os
kinds (ou um _/fail). Um tipo não-serializável não compila: o braço Ptr | ManyPtr | Channel | Function | RawPtr => fail(...) recusa na cara, em vez de gerar lixo.
O raciocínio
Seção intitulada “O raciocínio”A regra-raiz é o não-envenenamento levado ao limite: o escopo global não carrega função nenhuma.
Cada builtin seria um envenenador do namespace: conflita com nomes de usuário, polui o global, e te
faz decorar “é @size_of ou size_of?”. Mover tudo para reflect(...)/fail(...) mantém o namespace
limpo: metaprogramação é uma biblioteca que você importa, não mágica embutida.
Por que comptime é seguro de existir? Porque o que roda em compilação é quase forçado pela
arquitetura a ser o núcleo puro e determinístico da linguagem, sem IO, sem processos, sem
channels, sem syscalls. Comptime nunca lê um valor de runtime. Isso é o que deixa o compilador
otimizar agressivamente e garante reprodutibilidade, e é por isso que o escopo transient existe
(mut/lambdas são expressões transient, presas ao seu lugar, sem vazar do comptime para o runtime).
A exaustividade do match reflect.kind não é detalhe: é a aplicação do mesmo princípio do match de
domínio fechado (cap. 07) à derivação. E é uma lição aprendida na marra: a falta de um braço (Enum
faltando, depois String faltando) mordeu o design mais de uma vez, e a regra “todo match reflect.kind cobre o conjunto inteiro” virou checagem permanente.
Por que não as alternativas
Seção intitulada “Por que não as alternativas”- Macros (C). Substituição de texto: quebra léxico e estrutura, e você depura a macro como uma linguagem à parte. Comptime é execução semântica real, não texto.
- Builtins espalhados (
@sizeOf/@typeName, Zig). Cada um polui o global e vira “qual builtin é qual?”. Tudo sobreflect(...)é um namespace, não um vocabulário a memorizar. - Traits/derives implícitos (Rust). Sem
matchexaustivo, você descobre que um tipo “quase” satisfaz três camadas de compilação abaixo, com erro confuso. A derivação porreflect+ match exaustivo dá o erro no boundary, claro. - Deixar valores de comptime vazarem para runtime. Quebraria o determinismo e cegaria as otimizações; o escopo transient impede.
A dor concreta
Seção intitulada “A dor concreta”O macro C que não expandiu direito por um parêntese e você debuga às cegas. O programador novo de Zig parado em “qual é a função de reflexão mesmo?”, porque são vinte builtins. O derive de Rust que falha fundo na instanciação com uma mensagem que não aponta para o seu código. Cada uma é metaprogramação que virou um modo de pensar separado, com suas próprias regras e suas próprias surpresas.
O modelo mental
Seção intitulada “O modelo mental”Metaprogramação é biblioteca, não mágica. Comptime é o seu código rodando no compilador, puro e determinístico;
reflect(T)pergunta “que tipo é este?” sem nenhum builtin; omatchsobre o kind é exaustivo, então o compilador te força a tratar tudo.
A fricção aceita
Seção intitulada “A fricção aceita”Zero builtins custa verbosidade: você sempre escreve reflect(T).kind em vez de um @kind curto. E
comptime ser puro (sem IO, sem ler runtime) limita o que ele pode fazer, em troca de reprodutibilidade
total. A aposta é a de sempre: um pouco mais de texto, em nome de um namespace limpo e de uma fronteira
nítida entre o que roda na build e o que roda no programa.
Próximo: 09 · Strings